数据不一致是核心风险:事务回滚时缓存已删或提交后缓存未更新。正确做法是在db::transaction闭包外用try-catch,仅成功后清理缓存;避免在模型事件或路由绑定中提前操作缓存;队列中最小化事务范围;视图仅用于只读查询。

在 Laravel 8 中将数据库事务与缓存(尤其是 Redis)结合使用时,最核心的风险是数据不一致:事务回滚了,缓存却提前删了或没删;事务提交了,缓存却还留着旧值。这不是配置问题,而是执行时序和作用域没对齐导致的逻辑漏洞。
事务成功后才清理缓存,别在中间删
缓存失效必须严格绑定到事务的最终结果——只有 DB::transaction() 闭包执行完毕且无异常 时,才调用 Cache::forget()。不能把 Cache::forget() 写在事务闭包内部,更不能放在模型事件里(如 saving 或 updating),否则事务一旦回滚,缓存已丢,数据库却没变,用户下次读到的就是空或过期数据。
- ✅ 正确做法:事务闭包外用 try-catch 捕获,仅在成功路径中清理
- ✅ 推荐 key 命名:如
"user:{$id}"、"order:{$order->id}:summary",避免模糊通配 - ❌ 错误示例:
static::updating(fn ($u) => Cache::forget("user:{$u->id}"))—— 更新前就删,回滚后缓存丢失
别让路由模型绑定绕过缓存逻辑
如果控制器方法签名是 show(User $user),Laravel 会在进入 Cache::remember() 之前就查库加载模型,缓存完全失效。这虽不直接涉及事务,但会破坏“缓存+事务”整体的数据可信链。
- ✅ 改为接收原始 ID:
show($id),再在Cache::remember()闭包内调用User::find($id) - ✅ 确保路由定义不依赖类型提示,例如
Route::get('users/{id}', [UserController::class, 'show']) - ✅ 配合缓存键使用可预测结构,如
"user:{$id}:full",便于后续精准失效
队列任务里慎用事务包装整个 handle()
队列 worker 是常驻进程,复用数据库连接。若在 handle() 方法顶部写 DB::transaction(...),可能污染连接状态,导致“Transaction already active”或回滚失败。缓存清理若也塞在里面,风险加倍。
- ✅ 只在真正需要原子性的最小代码段包裹事务,例如只包
$order->update()和$log->save() - ✅ 缓存清理建议交给独立队列任务(延迟 0 秒),并确保队列驱动不是
sync - ✅ 若必须手动控制,用
DB::beginTransaction()+try/catch/finally,并在 finally 中检查DB::transactionLevel()后安全回滚
视图不能写,缓存也不能靠它更新
数据库视图(View)在事务中只支持 SELECT。若业务误用视图做 update() 或 save(),会直接报错中断事务;此时若已触发缓存删除,就会留下“DB 无变更、缓存已空”的脏状态。
- ✅ 所有写操作必须落在基础表,模型中显式指定
$table = 'real_table' - ✅ 视图仅用于事务内只读查询,例如
DB::select('SELECT * FROM user_summary WHERE id = ?', [$id]) - ✅ 若需缓存视图结果,应单独走读逻辑,且 key 设计要能被主表变更主动失效(比如打标签
Cache::tags(['user_summary'])->put(...))











