如何分析全量死锁
使用场景
- 数据库通过加锁保证并发事务的数据一致性,但加锁可能引发死锁——多个事务互相持有对方需要的锁,陷入循环等待,全部无法继续。
- 死锁在InnoDB中较为常见。传统定位方式是查看死锁日志,日志虽包含事务ID、SQL、锁模式和记录地址,但锁之间的关系、为何形成环并不直观,需要用户对InnoDB锁系统有较深理解才能分析,定位效率低、技术门槛高。
- DAS提供死锁可视化及分析的功能,将原始死锁日志还原为可读的拓扑图,帮助用户直观还原死锁现场、定位不合理的加锁,进而优化业务SQL预防死锁。核心能力包括:
- 可视化拓扑图:以事务和锁为节点,以持有、等待、冲突为边,直观展示事务之间的等待关系与死锁环。
- 锁信息展示:单击拓扑图上的锁节点,可查看锁的类型、模式、范围,以及锁住的具体数据(已将原始物理格式还原为可读值)。
- 日志分析:通过一键分析,回溯每个事务在死锁发生前后的完整SQL执行历史,帮助用户理解死锁的形成过程。
- 适用于以下场景:
- 数据库频繁报死锁错误(如`ERROR 1213`),需要快速定位死锁根因。
- 死锁涉及多个事务或多种锁类型,单看日志难以理清锁关系。
- 需要回溯死锁事务的完整SQL历史,找出加锁不合理的业务SQL并优化。
约束限制
- 已创建TaurusDB实例。
- 需要开启innodb_print_all_deadlocks参数。
- 需要开启全量死锁开关。
- 如需使用死锁日志分析功能,TaurusDB实例应提前开启全量SQL开关。
操作步骤
- 登录DAS管理控制台。
- 单击管理控制台左上角的
,选择区域和项目。 - 在左侧的导航栏中单击页签,进入DBA智能运维实例列表页面。
您也可以在产品概览页面,单击“进入DBA智能运维”,进入DBA智能运维实例列表页面。
- 在实例列表页面右上角,按照引擎、实例名称或者实例IP筛选实例。
- 选择目标实例,单击“详情”,选择“锁&事务 > 全量死锁分析”,查看列表。
- 选择对应发生时间的死锁,单击“查看详情”。如需查看可视化死锁,选择“死锁可视化”页签;如需回溯SQL,选择“死锁日志分析”页签。 图1 全量死锁分析
死锁可视化
传统定位死锁的方式是查看死锁日志。日志中能看到SQL语句、事务ID,但锁模式、锁住的记录地址等数据并不直观,尤其是这些锁之间是什么关系、如何形成环,需要用户对锁系统有较深理解才能高效分析。
DAS以可视化图的形式,直观展示死锁拓扑:以“事务”和“锁”为节点,以“持有”、“等待”、“冲突”为线,构成一个等待环。以下介绍典型死锁场景的拓扑图与构造方法。
以下示例均基于如下测试表(隔离级别为默认的`REPEATABLE READ`)。
- 构造测试表
CREATE TABLE dl_demo ( id BIGINT NOT NULL AUTO_INCREMENT, k VARCHAR(20), v INT, PRIMARY KEY (id), UNIQUE KEY uk_k (k) ) ENGINE=InnoDB; - 构造测试数据
INSERT INTO dl_demo(id, k, v) VALUES (1, 'a', 10), (2, 'b', 20), (3, 'c', 30);
以下示例中的SQL仅用于构造死锁数据,便于读者复现并对照拓扑图理解加锁行为。实际使用时,拓扑图由系统自动采集生成,无需手工构造。
示例一:两个事务发生死锁
最常见的死锁场景:两个事务分别持有一把行锁,又各自请求对方持有的行锁,形成环。
事务1持有id=1的行锁,事务2持有id=2的行锁;事务1请求id=2的行锁被事务2阻塞,事务2请求id=1的行锁被事务1阻塞,形成死锁。
拓扑图中,每个事务有两条边:一条持有边(连接其持有的锁,即“已授予”锁),一条等待边(连接其请求的锁,即“等待中”锁)。事务1持有的锁阻塞了事务2请求的锁,事务2持有的锁又阻塞了事务1请求的锁,两条冲突边连成一个环。
- 会话1
BEGIN; UPDATE dl_demo SET v = v + 1 WHERE id = 1;
- 会话2
BEGIN; UPDATE dl_demo SET v = v + 1 WHERE id = 2;
- 会话1(此时切换回会话1)
UPDATE dl_demo SET v = v + 1 WHERE id = 2;
- 会话2(此时切换回会话2,触发死锁)
UPDATE dl_demo SET v = v + 1 WHERE id = 1;
执行后,TaurusDB会自动检测到死锁并回滚其中一个事务。
示例二:等待解锁时引起死锁
TaurusDB中有一个容易忽略的特性:一把锁即使处于等待状态(尚未获取成功),同样可以阻塞其他锁的请求,这一点与操作系统中的锁有所不同。
在这种场景下,事务1请求的一把锁(处于等待状态)被事务2持有的锁阻塞,而事务1这把等待中的锁(插入意向锁)又反过来阻塞事务2的插入请求,造成死锁。拓扑图上体现为:两把处于等待状态的插入意向锁与各自对方持有的锁之间形成冲突边,构成环。
这种死锁通常发生在已持有的锁与插入意向锁之间:一个事务持有一段区间的锁,另一个事务尝试向该区间插入数据(请求插入意向锁,被已持有的锁阻塞进入等待),双方互相阻塞。
- 会话1
BEGIN; SELECT * FROM dl_demo WHERE k > 'a' AND k < 'b' FOR UPDATE;
- 会话2
BEGIN; SELECT * FROM dl_demo WHERE k > 'b' AND k < 'c' FOR UPDATE;
- 会话1(切换回会话1),向会话2持有间隙锁的区间插入,请求插入意向锁,被会话2的间隙锁阻塞,进入等待
INSERT INTO dl_demo(k, v) VALUES ('bb', 25); - 会话2(切换回会话2,触发死锁),向会话1持有间隙锁的区间插入,请求插入意向锁,被会话1的间隙锁阻塞 → 死锁
INSERT INTO dl_demo(k, v) VALUES ('ab', 15);
本场景的关键是双方各自持有一段不重叠的锁,再各自尝试插入到对方持有的锁范围内。插入意向锁被对方的锁阻塞,于是两把等待中的插入意向锁与两把已持有的锁两两冲突,构成死锁环。拓扑图上可观察到两把已授予的锁与两把等待中的插入意向锁,以及它们之间的冲突边。
示例三:三个事务发生死锁
三个事务参与的死锁,是两事务死锁的扩展:三个事务各自持有一把锁,又分别请求下一个事务持有的锁,首尾相接形成A→B→C→A的三元环。拓扑图上体现为三条持有边、三条等待边、三条冲突边,构成一个闭合的三角环。
- 会话1
BEGIN; UPDATE dl_demo SET v = v + 1 WHERE id = 1;
- 会话2
BEGIN; UPDATE dl_demo SET v = v + 1 WHERE id = 2;
- 会话3
BEGIN; UPDATE dl_demo SET v = v + 1 WHERE id = 3;
- 会话1(切换回会话1,此时会阻塞等待)
UPDATE dl_demo SET v = v + 1 WHERE id = 2;
- 会话2(切换回会话2,此时会阻塞等待)
UPDATE dl_demo SET v = v + 1 WHERE id = 3;
- 会话3(切换回会话3,触发死锁)
UPDATE dl_demo SET v = v + 1 WHERE id = 1;
执行后,三个事务的拓扑图上会呈现完整的三元等待环:事务1等待事务2、事务2等待事务3、事务3等待事务1,形成A→B→C→A的闭合环。
锁信息展示
死锁日志中的锁信息以原始物理格式呈现,包含:
- 锁模式(X/S)、锁状态(waiting/granted)、锁类型(记录锁/间隙锁/临键锁/插入意向锁)。
- 记录的物理地址:space id、page no、heap no。
- 记录所在表、索引。
- 锁住的数据:以十六进制字符串呈现(如`80000002`),难以直接读懂。
- 锁类型与模式:将日志中的锁描述文本,映射为结构化的锁类型(记录锁/间隙锁/临键锁/插入意向锁)与锁模式(X/S)。
- 锁住的数据:实时连接用户数据库获取表结构(列名、列类型、字符集、主键列、索引列),将锁记录中以十六进制呈现的原始物理值还原为可读的列值。
在拓扑图中单击对应的锁节点,可查看该锁的完整信息:锁类型、锁模式、锁状态、所在表与索引、锁住的数据、是否为主键索引上的锁。
死锁日志分析
死锁日志通常只记录触发死锁的那一条SQL,但死锁往往不是单条SQL导致的——事务中先前执行的SQL已经加上了某些锁,后续SQL才在已有锁的基础上触发冲突。因此,要看清死锁的形成过程,需要回溯参与死锁的事务在死锁发生前后的完整SQL执行历史。
DAS提供死锁日志分析能力,基于SQL洞察功能,自动回溯每个事务在死锁发生前后的SQL执行历史,帮助用户定位是哪条SQL加了不合理的锁。
使用方式
- 在死锁日志分析页面单击“开始分析”,系统启动分析任务。
- 系统以死锁发生时间为基准,结合各事务的活跃时长自动确定回溯时间范围,从SQL洞察任务解析后的数据中检索每个事务在该范围内的SQL执行历史。
- 分析任务异步执行,完成后可按事务维度查看该事务在死锁发生前后的SQL执行历史,包括SQL文本、执行时间、执行耗时、锁等待时间、影响行数、扫描行数等。
- 通过对照SQL执行历史与拓扑图中的锁信息,可以定位是哪条SQL加了不合理的锁,进而优化业务逻辑或SQL写法以预防死锁。