mysql通过innodb_print_all_deadlocks=on可记录死锁详情,thinkphp需开启log_sql/log_bind并注入请求上下文,死锁主因是事务顺序不一致、锁粒度失控及redis锁超时未释放。

为什么线上突然大量报 Deadlock found when trying to get lock
死锁不是数据库挂了,而是两个或多个事务互相等待对方持有的锁,MySQL 主动杀掉其中一个事务来破局。ThinkPHP 默认不暴露底层死锁错误细节,你看到的往往是 SQLSTATE[40001]: Serialization failure: 1213 Deadlock found when trying to get lock 这类 PDO 异常,但没上下文——不知道哪条语句、哪个事务、哪张表在争抢。
根本原因通常不在 SQL 写得“错”,而在事务边界不合理、更新顺序不一致、或高频并发下锁粒度失控。比如 A 事务先更新 user 再更新 order,B 事务反着来,就极易触发死锁。
- ThinkPHP 的
transaction()块默认不记录执行 SQL,出问题时只能靠猜 - MySQL 默认关闭慢查询日志,更不会记录死锁事件本身
- 线上环境直接开
general_log会严重拖慢性能,不能用
如何让 MySQL 主动记下每次死锁详情
MySQL 5.6.2+ 自带死锁日志开关,不依赖慢查,也不影响性能——它只在真实死锁发生时写入错误日志(error_log),不是实时监控。
只需在 MySQL 配置文件(如 /etc/my.cnf)的 [mysqld] 段落加一行:
innodb_print_all_deadlocks = ON
然后重启 MySQL 或执行 SET GLOBAL innodb_print_all_deadlocks = ON;(临时生效)。之后每次死锁都会在错误日志里留下完整事务堆栈,包括:TRANSACTION ID、涉及的 SQL、锁类型、等待/持有关系。
- 确认是否生效:查
SHOW VARIABLES LIKE 'innodb_print_all_deadlocks';,返回ON即可 - 日志位置由
log_error配置决定,常见路径如/var/log/mysql/error.log - 注意:该开关只输出 InnoDB 表的死锁,MyISAM 不适用
ThinkPHP 怎么把慢查询和事务上下文打到日志里
单纯开 MySQL 慢查日志(slow_query_log)不够——它只记耗时 SQL,不关联 PHP 请求 ID、用户 ID、控制器方法。你需要让 ThinkPHP 主动把关键上下文注入日志行。
在 config/database.php 中开启查询日志并定制格式:
'deploy' => 0,
'host' => '127.0.0.1',
// ... 其他配置
'params' => [
\PDO::ATTR_EMULATE_PREPARES => true,
],
'trigger_sql' => true, // 必须打开,否则不记录
'log_sql' => true, // 开启 SQL 日志
'log_bind' => true, // 记录绑定参数,避免 ? 看不懂
再配合自定义日志处理器,在 app/common.php 或中间件中注入请求标识:
Db::listen(function ($sql, $time, $explain) {
$request_id = request()->header('x-request-id', uniqid('req_'));
$controller = request()->controller();
$action = request()->action();
trace("[{$request_id}][{$controller}/{$action}] {$sql} [{$time}ms]", 'sql');
});
-
trace()输出到runtime/log/下,比直接 echo 更可控 - 务必启用
log_bind,否则UPDATE user SET status=? WHERE id=?看不到实际值 - 避免在高并发接口里用
Db::getLastSql()手动取 SQL,它不线程安全
定位死锁时最容易被忽略的三个点
很多团队查了半天,最后发现卡在看似无关的地方。
- ThinkPHP 的
save()默认走INSERT ... ON DUPLICATE KEY UPDATE,如果表有唯一索引但没主键,InnoDB 会升级为间隙锁(Gap Lock),大幅增加死锁概率 - 批量更新用
where in时,ThinkPHP 生成的 SQL 参数顺序不固定,不同请求传入 ID 数组顺序不同 → 导致加锁顺序不一致 → 死锁温床 - Redis 分布式锁没配好超时时间,导致 PHP 进程卡在
waitLock,事务迟迟不提交,把数据库锁占着不放,别人一碰就死锁
死锁日志里看到的“胜出者”不一定是问题代码,往往只是运气好一点;真正要盯的是那个反复出现在多条死锁记录里的业务逻辑块——比如优惠券核销、库存扣减、订单状态流转这类强一致性操作。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!










