如何从死锁日志中分析锁的持有情况?

分析死锁日志中锁的持有情况,需要重点关注日志中事务的 “持有锁(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
 

二、关键信息解析:如何识别锁的持有情况

1. 锁定对象:索引与表

日志中 “index [索引名] of table [库名].[表名]” 明确了锁关联的索引和表。例如:

  • index idx_c1 of table test.dl:表示锁在testdl表的idx_c1辅助索引上;
  • index PRIMARY of table test.dl:表示锁在表的主键索引上。

作用:确定事务操作的索引类型(主键 / 辅助索引),辅助索引操作通常会关联主键锁,这是死锁的常见诱因。

2. 锁的类型(lock_mode)

日志中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,表示它等待主键索引上的排他行锁。

3. 锁定的具体记录(PHYSICAL RECORD)

日志中PHYSICAL RECORD部分描述了被锁定的记录详情,包括:

  • heap no:记录在索引页中的位置(可理解为记录的 “行号”);
  • n_fields:记录包含的字段数;
  • (隐含)通过索引键值可推断锁定的具体记录(如idx_c1索引中c1=5的记录)。

作用:定位事务实际锁定的具体数据行,明确锁竞争的核心资源。

4. 事务的锁持有与等待关系

通过对比多个事务的HOLDSWAITING部分,可还原锁的竞争链:

  • 事务 A 持有锁 L1,等待锁 L2;
  • 事务 B 持有锁 L2,等待锁 L1;
  • 形成循环等待,触发死锁。

案例还原

  • 事务 1(更新操作)持有idx_c1索引的 X 锁,等待主键索引的 X 行锁;
  • 事务 2(删除操作)持有主键索引的 X 行锁,等待idx_c1索引的 X 锁;
  • 两者相互等待对方的锁,导致死锁。

三、分析步骤:从日志到锁持有逻辑

  1. 提取所有事务的操作 SQL:确定每个事务在执行什么操作(更新 / 删除 / 查询加锁),定位操作的条件字段(基于主键 / 辅助索引)。
  2. 梳理每个事务的 “持有锁”
    • 记录每个事务持有的锁类型(X/S)、关联的索引(主键 / 辅助索引)、锁定的记录范围(行锁 / 间隙锁);
    • 例如:事务 2 通过DELETE WHERE id=6持有主键索引的 X 行锁。
  3. 梳理每个事务的 “等待锁”
    • 记录等待的锁类型、关联的索引、等待的记录;
    • 例如:事务 1 通过UPDATE WHERE c1=5,先持有辅助索引锁,等待主键锁。
  4. 绘制锁竞争链
    • 用表格或流程图展示事务间的 “持有→等待” 关系;
    • 若存在 “A 持有 L1 等待 L2,B 持有 L2 等待 L1”,则确认死锁原因是加锁顺序相反

四、实战技巧:快速定位关键信息

  • 优先看索引类型:辅助索引 + 主键的混合锁容易引发死锁,需重点关注;
  • 锁定范围判断:间隙锁 /next-key 锁可能导致锁范围扩大,引发非预期冲突;
  • 结合表结构:通过表的索引设计(如辅助索引字段、主键类型),验证事务加锁的合理性(例如:辅助索引更新是否必须关联主键锁)。

总结

从死锁日志分析锁的持有情况,核心是拆解每个事务的 “持有锁” 和 “等待锁” 的类型、索引及记录信息,进而还原锁的竞争链。关键在于识别:事务持有的锁是什么(类型 / 索引 / 记录)、等待的锁是什么、多个事务的锁顺序是否相反。通过这种方法,可快速定位死锁根源(如加锁顺序不一致、索引设计不合理等),并针对性优化(如统一加锁顺序、优先使用主键操作)。

posted on 2025-07-09 09:03  数据库那些事儿  阅读(15)  评论(0)    收藏  举报