update() 静默跳过 updating/updated 事件,因其绕过模型实例化、直连 Query Builder 执行 SQL,不创建模型对象,故不触发任何模型生命周期事件;这是设计使然,非 bug。
为什么 update() 静默跳过 updating/updated 事件
因为 eloquent 的静态 update() 方法(如 user::where('status', 'pending')->update(['status' => 'active']))绕过了模型实例化,直接走 query builder 发 sql,根本不会创建 user 对象,自然不会触发任何模型事件。这不是 bug,是设计使然——它快,但“没感知”。
常见错误现象:dd() 放在 updating 里没输出、日志没记录、审计字段(如 updated_by)没更新、缓存没失效。
- 使用场景:后台批量审核、状态同步、定时任务中需要高效更新大量记录
- 参数差异:
update()接收纯数组,不校验$fillable,也不调用setAttributes()或访问器/修改器 - 性能影响:比逐条
save()快 5–10 倍(尤其万级数据),但牺牲了事件、强制类型转换、验证等模型层能力
怎么确认当前代码走的是 Query Builder 还是 Model 实例更新
最直接的办法是加一句日志或断点,看执行前有没有模型对象生成:
DB::listen(function ($query) {
if (Str::contains($query->sql, 'UPDATE `users`')) {
// 查看调用栈,找上一层是不是 User::update() 或 DB::table('users')->update()
\Log::info('Raw update detected', ['sql' => $query->sql, 'bindings' => $query->bindings]);
}
});
或者更简单:在模型的 boot() 里临时加个 static::updating(...) 并 dd('event fired'),如果没触发,基本确定是静态 update()。
- 容易踩的坑:混淆
User::query()->where(...)->update()和User::where(...)->get()->each->update(...)—— 后者会触发事件,但内存和性能代价极大 - 兼容性注意:Laravel 9+ 的
upsert()同样不触发事件,除非你手动 new 模型再save()
想保留事件又不想慢?用 chunkById() + 实例更新
这是平衡事件与性能最常用也最稳妥的方式:分批查出主键,再按主键取模型实例更新,既触发事件,又避免一次性加载全部模型到内存。
User::where('status', 'pending')
->chunkById(100, function ($users) {
foreach ($users as $user) {
$user->status = 'active';
$user->updated_by = auth()->id();
$user->save(); // 此时 updating/updated 会正常触发
}
});
- 为什么不用
cursorPaginate()?它依赖排序字段唯一性,且不能保证严格分片;chunkById()基于主键范围,更稳定 - 性能提示:100 条/批是经验值,太小(如 10)会导致查询次数暴涨;太大(如 1000)可能 OOM,尤其模型带关系或大字段
- 注意事务:如果业务要求原子性,需在外层包
DB::transaction(),但单批内失败不会回滚已提交的批次
真要直接 SQL 更新又想补事件?自己 dispatch
当性能压倒一切(比如百万级日志表清理),且你明确知道哪些业务逻辑必须跑(比如清空缓存、发通知),就别硬套模型事件,改为主动 dispatch:
User::where('status', 'pending')->update(['status' => 'active']);
// 手动触发等效逻辑
Cache::forget('users:pending');
Notification::route('mail', 'admin@example.com')
->notify(new BulkStatusChanged('pending', 'active'));
关键点不是“模拟事件”,而是识别出事件里真正做了什么——多数时候只是副作用操作,完全可以剥离出来独立调用。
- 容易被忽略的地方:事件里可能有数据库写入(如日志表插入),这类操作必须显式补上,不能只靠 cache/notify
- 不要封装成“伪事件”工具类来回调闭包——这会让调用链路模糊,后期难维护
- 如果多个地方都这么干,说明该把这批副作用逻辑抽成 service,由业务方法统一协调


















