DEADLOCKS

DEADLOCKS 表提供当前 TiDB 节点上最近发生的若干次死锁错误的信息。

USE information_schema; DESC deadlocks;
+--------------------+---------------------+------+------+---------+-------+ | Field | Type | Null | Key | Default | Extra | +--------------------+---------------------+------+------+---------+-------+ | DEADLOCK_ID | bigint(21) | NO | | NULL | | | OCCUR_TIME | timestamp(6) | YES | | NULL | | | RETRYABLE | tinyint(1) | NO | | NULL | | | TRY_LOCK_TRX_ID | bigint(21) unsigned | NO | | NULL | | | CURRENT_SQL_DIGEST | varchar(64) | YES | | NULL | | | KEY | text | YES | | NULL | | | TRX_HOLDING_LOCK | bigint(21) unsigned | NO | | NULL | | +--------------------+---------------------+------+------+---------+-------+

DEADLOCKS 表中需要用多行来表示同一个死锁事件,每行显示参与死锁的其中一个事务的信息。当该 TiDB 节点记录了多次死锁错误时,需要按照 DEADLOCK_ID 列来区分,相同的 DEADLOCK_ID 表示同一个死锁事件。需要注意,DEADLOCK_ID 并不保证全局唯一,也不会持久化,因而其只能在同一个结果集里表示同一个死锁事件。

DEADLOCKS 表中各列的字段含义如下:

  • DEADLOCK_ID:死锁事件的 ID。当表内存在多次死锁错误的信息时,需要使用该列来区分属于不同死锁错误的行。
  • OCCUR_TIME:发生该次死锁错误的时间。
  • RETRYABLE:该次死锁错误是否可重试。目前暂不支持收集可重试的死锁错误的信息,因而该字段值恒为 0。关于可重试的死锁错误的说明,参见可重试的死锁错误小节。
  • TRY_LOCK_TRX_ID:试图上锁的事务 ID,即事务的 start_ts
  • CURRENT_SQL_DIGEST:试图上锁的事务中当前正在执行的 SQL 语句的 Digest。
  • KEY:该事务试图上锁、但是被阻塞的 key,以十六进制编码的形式显示。
  • TRX_HOLDING_LOCK:该 key 上当前持锁并导致阻塞的事务 ID,即事务的 start_ts

要调整 DEADLOCKS 表中可以容纳的死锁事件数量,可通过 TiDB 配置文件中的 pessimistic-txn.deadlock-history-capacity 配置项进行调整,默认容纳最近 10 次死锁错误的信息。

示例 1

假设有如下表定义和初始数据:

create table t (id int primary key, v int); insert into t values (1, 10), (2, 20);

使两个事务按如下顺序执行:

事务 1事务 2说明
update t set v = 11 where id = 1;
update t set v = 21 where id = 2;
update t set v = 12 where id = 2;事务 1 阻塞
update t set v = 22 where id = 1;事务 2 报出死锁错误

接下来,事务 2 将报出死锁错误。此时,查询 DEADLOCKS 表,将得到如下结果:

select * from information_schema.deadlocks;
+-------------+----------------------------+-----------+--------------------+------------------------------------------------------------------+----------------------------------------+--------------------+ | DEADLOCK_ID | OCCUR_TIME | RETRYABLE | TRY_LOCK_TRX_ID | CURRENT_SQL_DIGEST | KEY | TRX_HOLDING_LOCK | +-------------+----------------------------+-----------+--------------------+------------------------------------------------------------------+----------------------------------------+--------------------+ | 1 | 2021-06-04 08:22:38.765699 | 0 | 425405959304904707 | 22230766411edb40f27a68dadefc63c6c6970d5827f1e5e22fc97be2c4d8350d | 7480000000000000385F728000000000000002 | 425405959304904708 | | 1 | 2021-06-04 08:22:38.765699 | 0 | 425405959304904708 | 22230766411edb40f27a68dadefc63c6c6970d5827f1e5e22fc97be2c4d8350d | 7480000000000000385F728000000000000001 | 425405959304904707 | +-------------+----------------------------+-----------+--------------------+------------------------------------------------------------------+----------------------------------------+--------------------+

该表中产生了两行数据,两行的 DEADLOCK_ID 字段皆为 1,表示这两行数据包含同一次死锁错误的信息。第一行显示 ID 为 425405959304904707 的事务,在 "7480000000000000385F728000000000000002" 这个 key 上,被 ID 为 "425405959304904708" 的事务阻塞了;第二行则显示 ID 为 "425405959304904708" 的事务在 "7480000000000000385F728000000000000001" 这个 key 上被 ID 为 425405959304904707 的事务阻塞了,构成了相互阻塞的状态,形成了死锁。

示例 2

假设查询 DEADLOCKS 表得到了如下结果集:

+-------------+----------------------------+-----------+--------------------+------------------------------------------------------------------+----------------------------------------+--------------------+ | DEADLOCK_ID | OCCUR_TIME | RETRYABLE | TRY_LOCK_TRX_ID | CURRENT_SQL_DIGEST | KEY | TRX_HOLDING_LOCK | +-------------+----------------------------+-----------+--------------------+------------------------------------------------------------------+----------------------------------------+--------------------+ | 1 | 2021-06-04 08:22:38.765699 | 0 | 425405959304904707 | 22230766411edb40f27a68dadefc63c6c6970d5827f1e5e22fc97be2c4d8350d | 7480000000000000385F728000000000000002 | 425405959304904708 | | 1 | 2021-06-04 08:22:38.765699 | 0 | 425405959304904708 | 22230766411edb40f27a68dadefc63c6c6970d5827f1e5e22fc97be2c4d8350d | 7480000000000000385F728000000000000001 | 425405959304904707 | | 2 | 2021-06-04 08:22:56.795410 | 0 | 425405961664462853 | 22230766411edb40f27a68dadefc63c6c6970d5827f1e5e22fc97be2c4d8350d | 7480000000000000385F728000000000000002 | 425405961664462854 | | 2 | 2021-06-04 08:22:56.795410 | 0 | 425405961664462854 | 22230766411edb40f27a68dadefc63c6c6970d5827f1e5e22fc97be2c4d8350d | 7480000000000000385F728000000000000003 | 425405961664462855 | | 2 | 2021-06-04 08:22:56.795410 | 0 | 425405961664462855 | 22230766411edb40f27a68dadefc63c6c6970d5827f1e5e22fc97be2c4d8350d | 7480000000000000385F728000000000000001 | 425405961664462853 | +-------------+----------------------------+-----------+--------------------+------------------------------------------------------------------+----------------------------------------+--------------------+

以上查询结果中的 DEADLOCK_ID 列表明,前两行共同表示一次死锁错误的信息,两条事务相互等待构成了死锁;而后三行共同表示另一次死锁信息,三个事务循环等待构成了死锁。

可重试的死锁错误

当事务 A 被另一个事务 B 已经持有的锁阻塞,而事务 B 直接或间接地被当前事务 A 持有的锁阻塞,将会引发一个死锁错误。这里:

  • 情况一:事务 B 可能(直接或间接地)被事务 A 开始后到被阻塞前这段时间内已经执行完成的语句产生的锁阻塞
  • 情况二:事务 B 也可能被事务 A 目前正在执行的语句阻塞

对于情况一,TiDB 将会向事务 A 的客户端报告死锁错误,并终止该事务;而对于情况二,事务 A 当前正在执行的语句将在 TiDB 内部被自动重试。例如,假设事务 A 执行了如下语句:

update t set v = v + 1 where id = 1 or id = 2;

事务 B 则先后执行如下两条语句:

update t set v = 4 where id = 2; update t set v = 2 where id = 1;

那么如果事务 A 先后对 id = 1id = 2 的两行分别上锁,且两个事务以如下时序运行:

  1. 事务 A 对 id = 1 的行上锁
  2. 事务 B 执行第一条语句并对 id = 2 的行上锁
  3. 事务 B 执行第二条语句试图对 id = 1 的行上锁,被事务 A 阻塞
  4. 事务 A 试图对 id = 2 的行上锁,被 B 阻塞,形成死锁

对于情况二,由于事务 A 阻塞其它事务的语句也是当前正在执行的语句,因而可以解除当前语句所上的悲观锁(使得事务 B 可以继续运行),并重试当前语句。TiDB 内部使用 key 的 hash 来判断是否属于这种情况。

当可重试的死锁发生时,内部自动重试并不会引起事务报错,因而对客户端透明,但是这种情况的频繁发生可能影响性能。当这种情况发生时,在 TiDB 的日志中可以观察到 single statement deadlock, retry statement 字样的日志。

CLUSTER_DEADLOCKS

CLUSTER_DEADLOCKS 表返回整个集群上每个 TiDB 节点中最近发生的数次死锁错误的信息,即将每个节点上的 DEADLOCKS 表内的信息合并在一起。CLUSTER_DEADLOCKS 还包含额外的 INSTANCE 列展示所属节点的 IP 地址和端口,用以区分不同的 TiDB 节点。

需要注意的是,由于 DEADLOCK_ID 并不保证全局唯一,所以在 CLUSTER_DEADLOCKS 表的查询结果中,需要 INSTANCEDEADLOCK_ID 两个字段共同区分结果集中的不同死锁错误的信息。

USE information_schema; DESC cluster_deadlocks;
+--------------------+---------------------+------+------+---------+-------+ | Field | Type | Null | Key | Default | Extra | +--------------------+---------------------+------+------+---------+-------+ | INSTANCE | varchar(64) | YES | | NULL | | | DEADLOCK_ID | bigint(21) | NO | | NULL | | | OCCUR_TIME | timestamp(6) | YES | | NULL | | | RETRYABLE | tinyint(1) | NO | | NULL | | | TRY_LOCK_TRX_ID | bigint(21) unsigned | NO | | NULL | | | CURRENT_SQL_DIGEST | varchar(64) | YES | | NULL | | | KEY | text | YES | | NULL | | | TRX_HOLDING_LOCK | bigint(21) unsigned | NO | | NULL | | +--------------------+---------------------+------+------+---------+-------+

SQL Digest

DEADLOCKS 表记录 SQL Digest,并不记录 SQL 原文。

SQL Digest 是 SQL 归一化之后的哈希值。如需查找 SQL Digest 对应的 SQL 原文,请进行以下任一操作:

  • 对于当前 TiDB 节点在最近一段时间内执行过的语句,你可以从 STATEMENTS_SUMMARYSTATEMENTS_SUMMARY_HISTORY 中根据 SQL Digest 查找到对应的 SQL 原文。
  • 对于整个集群所有 TiDB 节点在最近一段时间内执行过的语句,你可以从 CLUSTER_STATEMENTS_SUMMARYCLUSTER_STATEMENTS_SUMMARY_HISTORY 中根据 SQL Digest 查找到对应的 SQL 原文。
select digest, digest_text from information_schema.statements_summary where digest = "f7530877a35ae65300c42250abd8bc731bbaf0a7cabc05dab843565230611bb2";
+------------------------------------------------------------------+---------------------------------------+ | digest | digest_text | +------------------------------------------------------------------+---------------------------------------+ | f7530877a35ae65300c42250abd8bc731bbaf0a7cabc05dab843565230611bb2 | update `t` set `v` = ? where `id` = ? | +------------------------------------------------------------------+---------------------------------------+

关于 SQL Digest 和 STATEMENTS_SUMMARYSTATEMENTS_SUMMARY_HISTORYCLUSTER_STATEMENTS_SUMMARYCLUSTER_STATEMENTS_SUMMARY_HISTORY 表的详细说明,请参阅 Statement Summary Tables