Hyperf3.1批量更新默认不触发模型事件,因update()直接执行SQL、不实例化模型;需用get()+save()循环或事务封装才能触发事件,JSON字段需手动json_encode(),大数据量应分批更新防锁表。

Hyperf3.1中批量更新为何不触发模型事件
Hyperf3.1的模型批量更新默认跳过事件钩子,这是设计使然而非Bug。当你调用User::where('status', 0)->update(['status' => 1])时,框架直接生成SQL执行,不实例化模型、不走boot()、不触发updating或updated等事件。
想让事件生效,必须走单条模型实例路径:先find()或get()取出模型对象,再逐个调用save()。这会触发完整生命周期,但性能下降明显。
【绕过事件是默认行为,不是疏漏】——Hyperf沿用Laravel风格,把update()定位为“原生SQL直写”,和ThinkPHP的Model::update()逻辑一致。
Hyperf3.1里真正能触发事件的批量更新怎么做
方法一:循环+事务封装(强一致性场景)
第一步:用DB::transaction()包裹整个操作块,避免部分成功部分失败。
第二步:查出所有待更新记录$users = User::where('status', 0)->get(),注意这里必须用get()而非cursor(),否则无法后续调用save()。
第三步:遍历修改并保存:foreach ($users as $user) { $user->status = 1; $user->save(); }。每条记录都会触发updating→updated事件链。
方法二:用upsert()配合唯一索引(Laravel 9+兼容,Hyperf3.1已支持)
DB::table('users')->upsert($data, ['id'], ['status', 'updated_at'])——它底层调用INSERT ... ON DUPLICATE KEY UPDATE,不走模型层,因此也不触发事件。此法仅适合纯数据覆盖,别指望updated回调。
Hyperf3.1批量更新JSON字段的坑与填法
Hyperf模型对JSON字段做了$casts = ['config' => 'array']声明后,单条save()能自动序列化;但update()批量操作时,这个转换完全失效。
错误写法:User::where('id', 1)->update(['config' => ['theme' => 'dark']]) → 数据库存入字符串"Array"而非JSON。
正确写法:手动json_encode():User::where('id', 1)->update(['config' => json_encode(['theme' => 'dark'], JSON_UNESCAPED_UNICODE)])。
如果要批量更新多条且含JSON字段,必须提前对JSON字段做array_map()编码,不能依赖模型自动处理。
Hyperf3.1分批更新防锁表实操
当ID列表超过500条时,直接whereIn('id', $ids)->update()可能触发MySQL间隙锁,导致其他查询阻塞。
① 将ID数组切分为每组300个:$chunks = array_chunk($ids, 300)。
② 对每个分块单独执行更新:foreach ($chunks as $chunk) { User::whereIn('id', $chunk)->update(['status' => 2]); }。
③ 若需事务保障,给每个foreach块套上DB::transaction(),但注意不要把全部分块包进一个事务——那会延长锁持有时间。
这一步做完后,数据库里对应记录的状态已全部更新完毕。


















