laravel 的 update() 方法不自动更新 updated_at,需用 touch() 批量刷新时间戳、update() 手动传 now() 或 each()+save()(仅需事件时);逐条 save() 性能差,应优先下压逻辑到 sql。

update() 方法不会自动更新 updated_at 怎么办
Laravel 的 update() 静态方法(如 User::where(...)->update([...]))直接执行 SQL UPDATE,绕过模型生命周期,因此 updated_at 不会自动更新,也不会触发 updating/updated 事件。
如果你需要保留时间戳自动更新和事件机制,就不能用批量 update(),而得走模型实例路线。但注意:逐条 save() 性能差,100 条可能触发 100 次查询 + 100 次时间戳赋值 + 100 次事件。
- ✅ 真正需要事件或访问器/修改器时,用
each()+save() - ✅ 只需更新时间戳、不关心事件,且数据量不大(touch()
- ❌ 直接
update(['updated_at' => now()])虽然能写时间,但会跳过timestamps逻辑(比如你重写了getUpdatedAtColumn()或用了自定义字段名)
用 touch() 批量触碰 updated_at 最省事
touch() 是 Laravel 提供的专用方法,它只更新时间戳字段(默认 updated_at),不碰其他属性,也不触发模型事件 —— 正好适合“仅刷新时间戳”的场景。
它底层仍是单条 UPDATE,但语义清晰、兼容性好(支持软删除、自定义时间戳字段等):
$ids = [1, 2, 5, 10];
User::whereIn('id', $ids)->touch(); // 自动更新所有匹配记录的 updated_at
- ✅ 支持自定义时间戳字段:在模型中设
const UPDATED_AT = 'last_modified';,touch()仍生效 - ✅ 若启用了软删除,
touch()会忽略已删除记录(除非加withTrashed()) - ⚠️ 注意:不能同时触碰
created_at;若真要改两个字段,得回退到update()并手动传值
想同时更新其他字段 + 时间戳?用 update() 加手动 now()
当你要批量更新普通字段(比如 status)并确保 updated_at 也刷新,最直接的方式是显式传入时间值:
User::where('active', 1)
->update([
'status' => 'processed',
'updated_at' => now(),
])
这是安全、高效、无副作用的做法。Laravel 的 now() 返回的是数据库兼容格式(Carbon 实例,转成字符串时自动适配 MySQL/PostgreSQL 的时间格式)。
- ✅ 不依赖模型逻辑,适合高频、大数据量更新(如后台任务)
- ✅ 兼容时区:只要 DB 连接配置了
'timezone' => '+08:00',now()就按该时区生成值 - ❌ 不会调用
setUpdatedAtAttribute()修改器,如有自定义逻辑(比如转为秒级时间戳),需手动处理
为什么不要用 foreach + save() 更新上百条
看起来直观:User::find($ids)->each(fn($u) => $u->status = 'done' && $u->save());,但它在真实项目里容易成为性能瓶颈。
- ❌ 每次
save()都会:查一次原数据(如果没禁用)、跑验证(即使没定义规则)、触发全部事件、重新计算所有casts和mutators - ❌ 数据库连接数暴涨,尤其在队列任务中可能触发连接池耗尽
- ✅ 唯一适用场景:必须逐条做业务判断(比如“只有 status 为 pending 的才更新”,且逻辑无法下推到 WHERE)
真正要兼顾逻辑与性能,应该把条件尽量下压到 SQL 层,再配合 update() 或 touch() —— 时间戳不是魔法,它只是个字段,按需写入即可。











