SQL死锁是指两个或多个事务在同一资源上相互占用,并请求锁定对方占用的资源,从而导致恶性循环的现象,数据库检测到死锁后会抛出Deadlock detected错误并回滚其中一个事务。不同数据库记录的死锁日志格式略有差异,但核心信息结构基本一致,掌握通用的解读模板可以快速定位问题。

死锁日志核心结构
大部分关系型数据库的死锁日志都包含以下几个核心部分,解读时按优先级依次查看:
- 死锁发生时间:记录死锁出现的具体时间戳,用于关联业务操作时间线
- 参与死锁的事务列表:列出所有卷入死锁的事务ID、执行线程、客户端信息等
- 资源等待链:描述每个事务持有的锁和等待的锁,是定位冲突的核心
- 回滚事务信息:数据库选择回滚的事务标识及原因
通用日志解读模板
以下以MySQL的死锁日志为例,展示标准化的解读步骤:
步骤1:提取基础信息
首先记录死锁发生的基本信息,建立排查上下文:
-- 示例死锁日志开头部分 2024-05-20 14:32:15 0x7f8a1b2c3d4e [Note] InnoDB: Transactions deadlock detected, dumping detailed information. *** (1) TRANSACTION: TRANSACTION 123456, ACTIVE 2 sec starting index read mysql tables in use 1, locked 1 LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s) MySQL thread id 89, OS thread handle 140238848694016, query id 12345 localhost root updating
从这段日志可以提取:死锁时间2024-05-20 14:32:15,参与事务1的ID是123456,活跃时长2秒,操作的是1张表,目前处于锁等待状态,对应线程ID是89。
步骤2:分析事务持有的锁和等待的锁
这是解读的核心部分,需要明确每个事务已经拿到了什么锁,还在等什么锁:
*** (1) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 58 page no 3 n bits 72 index PRIMARY of table `test`.`order` trx id 123456 lock_mode X waiting *** (2) TRANSACTION: TRANSACTION 123457, ACTIVE 3 sec starting index read mysql tables in use 1, locked 1 3 lock struct(s), heap size 1136, 2 row lock(s) MySQL thread id 90, OS thread handle 140238848694017, query id 12346 localhost root updating *** (2) HOLDS THIS LOCK(S): RECORD LOCKS space id 58 page no 3 n bits 72 index PRIMARY of table `test`.`order` trx id 123457 lock_mode X *** (2) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 58 page no 5 n bits 72 index idx_user_id of table `test`.`order` trx id 123457 lock_mode X waiting
解读这段内容:事务1(123456)正在等待order表主键索引上的X锁(排他锁);事务2(123457)已经持有了order表主键索引上的X锁,同时正在等待order表idx_user_id索引上的X锁。结合两个事务的等待关系,就能梳理出死锁的资源循环等待链。
步骤3:定位回滚事务及SQL
日志最后会说明数据库选择回滚的事务以及对应的执行SQL:
*** WE ROLL BACK TRANSACTION (1) *** (1) SQL statement that caused the deadlock: UPDATE order SET status = 1 WHERE id = 100 *** (2) SQL statement that caused the deadlock: UPDATE order SET user_id = 20 WHERE id = 101
这里数据库回滚了事务1,对应的SQL是更新id=100的订单状态,事务2的SQL是更新id=101的订单的用户ID,两个SQL操作了不同的行但触发了索引锁冲突,最终形成死锁。
标准化排查流程总结
按照以下流程可以快速完成死锁排查:
- 从日志中提取死锁发生时间、涉及的事务ID和线程信息
- 逐个梳理每个事务持有的锁类型、锁对应的表和资源
- 梳理事务之间的锁等待关系,画出等待链确认循环等待逻辑
- 关联对应的执行SQL,分析SQL的加锁逻辑是否符合预期
- 优化SQL执行顺序、索引设计或事务粒度,避免同类死锁再次发生
不同数据库日志差异说明
不同数据库的死锁日志细节略有不同,但核心逻辑一致:
| 数据库类型 | 日志获取方式 | 核心差异点 |
|---|---|---|
| MySQL | 查看error.log或使用SHOW ENGINE INNODB STATUS | 会明确列出每个事务的等待锁和持有锁的索引信息 |
| PostgreSQL | 查看log_min_duration_statement相关日志 | 会记录死锁涉及的进程ID和等待的锁类型 |
| SQL Server | 使用系统存储过程或扩展事件捕获 | 会记录死锁涉及的对象的页号和锁资源哈希 |
常见死锁场景及优化建议
结合日志解读结果,常见的死锁优化方向包括:
- 如果日志显示多个事务按不同顺序更新同一批数据,统一事务内SQL的执行顺序
- 如果日志显示锁等待集中在非主键索引,评估是否可以优化索引减少锁范围
- 如果事务持有锁的时间过长,缩小事务粒度,避免长事务占用资源
- 对于高并发的更新场景,可以考虑使用乐观锁替代悲观锁减少冲突
掌握这套日志解读模板后,遇到Deadlock detected报错时,只需要按步骤提取日志中的对应字段,就能快速定位死锁的根因,大幅提升排查效率。