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