Redis 缓存同步至数据库时的 N+1 问题

原写法

foreach ($dates as $user_id => $actived_at) {
    // 会将 `user_1` 转换为 1
    $user_id = str_replace($this->field_prefix, '', $user_id);

    // 只有当用户存在时才更新到数据库中
    if ($user = $this->find($user_id)) {
        $user->last_actived_at = $actived_at;
        $user->save();
    }
}

一条缓存入库需要两次查询,若网站用户量大(以十万用户计),每日 0 点缓存数据入库时会产生二十万条语句。我没有测试过需要多少时间,但是这期间 users 表会被写锁锁死,影响数据库性能。

修改一下代码,用 mysql 的 UPDATE...CASE...WHEN...THEN 更新语句,缓存入库只需要一条语句:

...
use DB;
...
public function syncUserActivedAt() {
    ...
    $data = Redis::hGetAll($hash);  //从 Redis 中获取所有哈希表里的数据
    $this->batchUpdate($data);
    ...
}

private function batchUpdate($data) {
    $sql = 'UPDATE `users` SET last_active_at=CASE id';
    foreach ($data as $key => $activeTime) {
        $user_id = str_replace($this->fieldPrefix, '', $key);   //将user_1转为1
        $sql .= ' WHEN '.$user_id.' THEN "'.$activeTime.'"';
    }
    $sql .= ' ELSE last_active_at END';
    DB::update($sql);
}

另外要注意的是这条长语句会不会长到 mysql 的语句长度上限

《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
讨论数量: 2

你的sql也不完美,首先要按你说的10万个用户,那这个sql还要好多分段,另外用 case语句有个致命的问题,它会更新所有行,这样又还得通过in语句来过滤。所以还不如写的简单一点,另外如果一个网站有10万用户,日活最多2000了不起了

4年前 评论

这个语句会锁表吧

2年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!