分析死锁日志中锁的持有情况,需要重点关注日志中事务的 “持有锁(HOLDS THE LOCK (S))” 和 “等待锁(WAITING FOR THIS LOCK TO BE GRANTED)” 部分,结合锁的类型、关联的索引和记录信息,还原事务间的锁竞争关系。以下是具体分析方法和步骤:
MySQL 死锁日志(通过SHOW ENGINE INNODB STATUS获取)中,与锁持有情况相关的关键部分包括:
- 每个事务的基本信息(事务 ID、状态、执行的 SQL);
HOLDS THE LOCK(S):该事务已持有的锁;
WAITING FOR THIS LOCK TO BE GRANTED:该事务正在等待的锁。
以典型死锁日志片段为例:
*** (1) TRANSACTION:
TRANSACTION 1416764, ACTIVE 15 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 5 lock struct(s), heap size 1128, 3 row lock(s)
query: update dl set c2=10 where c1=5
*** (1) HOLDS THE LOCK(S):
RECORD LOCKS space id 49 page no 5 n bits 80 index idx_c1 of table `test`.`dl`
trx id 1416764 lock_mode X
Record lock, heap no 8 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 49 page no 4 n bits 80 index PRIMARY of table `test`.`dl`
trx id 1416764 lock_mode X locks rec but not gap waiting
Record lock, heap no 7 PHYSICAL RECORD: n_fields 5; compact format; info bits 32
日志中 “index [索引名] of table [库名].[表名]” 明确了锁关联的索引和表。例如:
index idx_c1 of table test.dl:表示锁在test库dl表的idx_c1辅助索引上;
index PRIMARY of table test.dl:表示锁在表的主键索引上。
作用:确定事务操作的索引类型(主键 / 辅助索引),辅助索引操作通常会关联主键锁,这是死锁的常见诱因。
日志中lock_mode字段标识锁的类型,常见类型包括:
lock_mode X:排他锁(事务独占资源,其他事务不能读 / 写);
lock_mode S:共享锁(事务可读,其他事务可加共享锁但不能加排他锁);
lock_mode X locks gap:间隙锁(锁定索引范围,防止插入);
lock_mode X locks rec but not gap:行锁(仅锁定单条记录,不包含间隙);
lock_mode X next-key lock:next-key 锁(行锁 + 间隙锁,默认加锁单位)。
案例解析:
- 日志中事务 1 的
HOLDS THE LOCK(S)显示lock_mode X,表示它持有idx_c1索引上的排他锁;
- 事务 1 的
WAITING FOR显示lock_mode X locks rec but not gap waiting,表示它等待主键索引上的排他行锁。
日志中PHYSICAL RECORD部分描述了被锁定的记录详情,包括:
heap no:记录在索引页中的位置(可理解为记录的 “行号”);
n_fields:记录包含的字段数;
- (隐含)通过索引键值可推断锁定的具体记录(如
idx_c1索引中c1=5的记录)。
作用:定位事务实际锁定的具体数据行,明确锁竞争的核心资源。
通过对比多个事务的HOLDS和WAITING部分,可还原锁的竞争链:
- 事务 A 持有锁 L1,等待锁 L2;
- 事务 B 持有锁 L2,等待锁 L1;
- 形成循环等待,触发死锁。
案例还原:
- 事务 1(更新操作)持有
idx_c1索引的 X 锁,等待主键索引的 X 行锁;
- 事务 2(删除操作)持有主键索引的 X 行锁,等待
idx_c1索引的 X 锁;
- 两者相互等待对方的锁,导致死锁。
-
提取所有事务的操作 SQL:确定每个事务在执行什么操作(更新 / 删除 / 查询加锁),定位操作的条件字段(基于主键 / 辅助索引)。
-
梳理每个事务的 “持有锁”:
- 记录每个事务持有的锁类型(X/S)、关联的索引(主键 / 辅助索引)、锁定的记录范围(行锁 / 间隙锁);
- 例如:事务 2 通过
DELETE WHERE id=6持有主键索引的 X 行锁。
-
梳理每个事务的 “等待锁”:
- 记录等待的锁类型、关联的索引、等待的记录;
- 例如:事务 1 通过
UPDATE WHERE c1=5,先持有辅助索引锁,等待主键锁。
-
绘制锁竞争链:
- 用表格或流程图展示事务间的 “持有→等待” 关系;
- 若存在 “A 持有 L1 等待 L2,B 持有 L2 等待 L1”,则确认死锁原因是加锁顺序相反。
- 优先看索引类型:辅助索引 + 主键的混合锁容易引发死锁,需重点关注;
- 锁定范围判断:间隙锁 /next-key 锁可能导致锁范围扩大,引发非预期冲突;
- 结合表结构:通过表的索引设计(如辅助索引字段、主键类型),验证事务加锁的合理性(例如:辅助索引更新是否必须关联主键锁)。
从死锁日志分析锁的持有情况,核心是拆解每个事务的 “持有锁” 和 “等待锁” 的类型、索引及记录信息,进而还原锁的竞争链。关键在于识别:事务持有的锁是什么(类型 / 索引 / 记录)、等待的锁是什么、多个事务的锁顺序是否相反。通过这种方法,可快速定位死锁根源(如加锁顺序不一致、索引设计不合理等),并针对性优化(如统一加锁顺序、优先使用主键操作)。