PHP 8.2 不引发死锁,死锁源于 MySQL InnoDB 高并发下加锁顺序不一致;需通过 SHOW ENGINE INNODB STATUS 定位死锁现场,修复索引、SQL、事务重试逻辑及统一加锁顺序。

PHP 8.2 本身不引发死锁,死锁是 MySQL InnoDB 在高并发下多个事务争抢行锁、间隙锁时因加锁顺序不一致导致的循环等待。排查修复重点在数据库层与 PHP 逻辑协同——不是改 PHP 版本,而是让应用更健壮、SQL 更精准、事务更轻量。
查清死锁现场:用 SHOW ENGINE INNODB STATUS
这是最直接有效的诊断方式,不依赖日志开关,立刻看到最近一次死锁全貌:
- 在 MySQL 命令行或 PHPMyAdmin 的 SQL 标签页执行:SHOW ENGINE INNODB STATUS\G
- 定位输出中的 LATEST DETECTED DEADLOCK 段落
- 重点关注:
- 两个事务各自的 SQL(特别是 WHERE 条件值,比如
WHERE id = 123或WHERE order_no = 'ORD-001') - 各自持有的锁(如
RECORD LOCKS on index PRIMARY of table `db`.`orders`) - 各自等待的锁(如
WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS on index idx_user_id)
- 两个事务各自的 SQL(特别是 WHERE 条件值,比如
- 若发现一个事务锁
PRIMARY、另一个锁idx_user_id,大概率是外键列没建索引;若都锁在idx_created_at但条件含范围查询,说明联合索引设计不合理
修复 PHP 代码中的重试逻辑
死锁发生后 MySQL 会自动回滚一个事务并报错 1213,PHP 必须捕获并重试,不能靠前端刷新或 Nginx 重发:
- 确保 PDO 启用异常模式:
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); - 重试最多 3 次,超过说明底层设计有问题
- 每次重试前必须重新开启事务:
$pdo->beginTransaction(),不能在已中断的事务里继续 - 避免固定 sleep,推荐指数退避:
usleep(50000)→usleep(100000)→usleep(200000)
收敛锁范围:索引和 SQL 必须匹配
锁不住该锁的行,就会扩散到间隙甚至整张表。关键看 WHERE 条件是否命中索引:
立即学习“PHP免费学习笔记(深入)”;
- 用
EXPLAIN检查 UPDATE/SELECT … FOR UPDATE 的执行计划,确认type是range或ref,不是ALL或index - 联合索引字段顺序要匹配高频查询:等值条件放左(如
user_id),范围条件放右(如created_at > ?) - 外键列必须单独建索引,否则关联更新时极易触发间隙锁冲突
- 避免在事务中做
file_get_contents、curl_exec、复杂计算等耗时操作——锁不会因为你正在发 HTTP 请求而释放
统一加锁顺序:从源头杜绝循环等待
多个事务操作多张表时,顺序不一致是死锁主因。例如订单业务,所有地方都必须严格按同一路径执行:
- 约定死:user → order → order_item → payment_log
- 禁止出现 A 接口先更新
order再扣inventory,B 接口反过来先扣inventory再更新order - 即使单表多行更新,也要按主键升序排列 ID,比如
WHERE id IN (100, 50, 80)改为WHERE id IN (50, 80, 100),减少间隙锁交叉



















