PHP中SELECT FOR UPDATE并发死锁的根本原因是多个事务以不同顺序对同一组记录加锁,导致InnoDB检测到循环等待后回滚一个事务并报错1205;需开启innodb_print_all_deadlocks并查错误日志定位死锁链,同时确保事务包裹完整、索引命中、避免长耗时操作。

PHP 中 SELECT FOR UPDATE 为什么一并发就死锁
根本原因是多个事务以不同顺序对同一组记录加锁,MySQL InnoDB 检测到循环等待后主动回滚其中一个事务,抛出 SQLSTATE[HY000]: General error: 1205 Deadlock found when trying to get lock。这不是 PHP 环境特有,但 phpEnv(集成环境)常默认开启 innodb_print_all_deadlocks = OFF,导致你只看到报错,看不到完整死锁链。
必须立刻确认两点:
-
SHOW VARIABLES LIKE 'innodb_print_all_deadlocks';—— 若为OFF,需在 my.cnf 中设为ON并重启 MySQL -
SHOW VARIABLES LIKE 'log_error';—— 定位错误日志路径,死锁详情全在里面,不是SHOW ENGINE INNODB STATUS\G那次性的
常见诱因:where id in (3,1,5) 这类无序 ID 列表,InnoDB 实际加锁顺序可能按索引物理顺序走,A 事务锁 1→3→5,B 事务锁 5→1→3,极易成环。
用 DB::transaction() 包裹 lockForUpdate() 仍失败?检查这三点
Laravel 的 lockForUpdate() 不是“自动加锁”,它只是拼 SQL;真正起作用的是整个事务块是否包裹住「读→改→写」全过程。漏掉任意一环,都会退化成普通更新,引发丢失修改或死锁。
立即学习“PHP免费学习笔记(深入)”;
务必核对:
- 事务外不能调用
$user->save()——save()会发起新事务,脱离原锁上下文 -
where条件必须命中索引,否则SELECT ... FOR UPDATE会升级为表锁,多事务一碰就锁死 - 不要在事务中做 HTTP 请求、文件写入、日志记录等耗时操作 —— 锁持有时间越长,死锁概率指数上升
正确写法示例:
DB::transaction(function () {
$user = User::where('id', 123)->lockForUpdate()->firstOrFail();
$user->balance -= 100;
// 所有业务校验(如余额是否足够)必须在此处完成
$user->save(); // 此次 update 在同一事务内,复用前面的行锁
});乐观锁替代方案:用 increment() 或带 where version = ? 的 update()
悲观锁(FOR UPDATE)适合强一致性、写少读多;但 phpEnv 下若并发量稍高(比如秒杀、抢券),频繁加锁+等待+重试反而拖垮吞吐。此时应切换思路:放弃“先锁再改”,改用“原子更新+版本校验”。
两种安全做法:
- 纯数值变更(如库存扣减):直接用
User::where('id', 123)->increment('balance', -100);—— 底层是UPDATE ... SET balance = balance - 100,单条语句原子执行,不依赖锁 - 含业务逻辑的更新(如状态流转 + 版本校验):必须用
whereRaw('version = ?', [$oldVersion]),且update()返回影响行数。若为 0,说明已被别人抢先更新,需重试或拒绝
绝对禁止:$model->version++ 后 $model->save() —— 两步非原子,两个请求读到相同 version=5,都写成 6,一次更新彻底丢失。
phpEnv 中死锁重试不能只靠 try/catch 盲目循环
捕获 PDOException 错误码 1213 是必要动作,但重试本身有风险边界:
- 只对幂等操作重试:扣库存、状态从
pending→processing可重试;发短信、写外部日志、调支付接口绝不可重试 - 必须指数退避:第一次 100ms,第二次 200ms,第三次 400ms,避免重试风暴压垮数据库
- Laravel 8.0+ 的
DB::transaction(..., $attempts)自动重试仅适用于简单场景;复杂逻辑(如跨表校验、条件分支)仍需手写重试封装
最容易被忽略的一点:重试前必须重新查询最新数据(包括锁住的记录),而不是复用上一轮缓存的模型实例 —— 否则你是在拿过期快照反复尝试,大概率再次死锁。



















