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 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
讨论数量: 2

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

5年前 评论

这个语句会锁表吧

3年前 评论