TiDB 锁冲突问题处理:从等待链定位到事务改进

原文:PingCAP 及 TiDB 文档贡献者,TiDB 锁冲突问题处理。中文核查与整理:未完纪。2026-10-05 核对时 stable 页面标示 v8.5;同时完整读取官方仓库 release-8.5 正文,避免把 master 文档直接当作稳定版。以下 SQL、命令和日志均未在本环境执行。

TiDB 从 v3.0 起提供乐观与悲观两种事务模式。锁冲突既可能表现为一批事务等同一个 key,也可能是两条事务互相阻塞,或者事务持续太久、锁的 TTL 已经失效。排查时应先识别发生的是哪一种情形,再选择事务重试、业务减冲突或参数调整;单纯延长超时并不能解释冲突原因。

事务A持有key1并等待事务B持有的key2,事务B持有key2并等待key1,构成死锁环。Lock View用等待事务、持锁事务、key和SQL摘要还原关系。
未完纪绘制:两事务死锁环及 Lock View 的观察字段。不是实际数据库截图。

先理解 Lock View 的观察范围

从 v5.1 开始,TiDB 在 information_schema 中提供 Lock View 系统表。它只提供悲观锁的冲突和等待信息,不能把表中没有记录当成“没有锁冲突”的证据。

表 用途
TIDB_TRX / CLUSTER_TIDB_TRX 当前节点或集群中正在运行的事务,包括等锁状态、开始等待时间以及执行过的 SQL Digest。
DATA_LOCK_WAITS 从 TiKV 获取悲观锁等待关系:等待者、持锁者的事务时间戳、当前 SQL 摘要和等待的 key。
DEADLOCKS / CLUSTER_DEADLOCKS 当前节点或集群最近记录的死锁环、事务关系、SQL 摘要与冲突 key。

这些表里的 SQL 文本由摘要查询得到,已经归一化,格式和参数都被移除。例如 WHERE id = ? 不能告诉你原调用传入了哪个业务 ID。事务表只反映查询时仍在运行的事务;事务结束后可能查不到。跨表 JOIN 获取的数据也不保证来自同一时刻,结果不完整或看似不一致时,需要结合时间与其他证据解释。

编辑补充:系统表、日志和 key 解码可能暴露库名、表名、主键、会话与用户信息,应使用获授权的诊断账户并在对外分享时脱敏;不要为了方便把业务日志公开。

用死锁表还原等待环

SELECT * FROM information_schema.deadlocks;

这条原文查询读取本节点最近记录的死锁,跨节点可查 CLUSTER_DEADLOCKS。原文的示例中,两条记录拥有同一个 DEADLOCK_ID = 1:

TRY_LOCK_TRX_ID TRX_HOLDING_LOCK KEY_INFO 中的行
426812829645406216 426812829645406217 等待表 t 的 handle_value 2
426812829645406217 426812829645406216 等待表 t 的 handle_value 1

两边都在执行形如 UPDATE t SET v = ? WHERE id = ? 的语句:第一个事务等第二个,第二个又等第一个,因此形成死锁。原始结果还包括 OCCUR_TIME、RETRYABLE、CURRENT_SQL_DIGEST、CURRENT_SQL_DIGEST_TEXT、原始 KEY 与解析后的 KEY_INFO。上表仅整理原文 2021 年的示例,不是本次查询结果;不能根据这些示例 ID 操作真实会话。

按 key 聚合,识别热点排队

如果大量事务都在等少数 key,可以先聚合等待记录:

SELECT `key`, COUNT(*) AS `count`
FROM information_schema.data_lock_waits
GROUP BY `key`
ORDER BY `count` DESC;

多次、适度间隔地采样,有助于区分持续热点和偶发等待,不能把一次计数当成整个时间段的争用量。找到目标 key 后,再关联正在等待的事务:

SELECT trx.*
FROM information_schema.data_lock_waits AS l
LEFT JOIN information_schema.cluster_tidb_trx AS trx
  ON l.trx_id = trx.id
WHERE l.`key` = '7480000000000000415F728000000000000001'
LIMIT 100;

这是对原文查询的编辑修正:字符串改用单引号,避免受 SQL 模式影响;增加 LIMIT 100 控制展示结果。示例十六进制 key 需要替换为诊断获得的目标,应用中应采用绑定参数,不能把任意外部输入拼进 SQL。原文将这一段描述为筛选“等锁时间较长”的事务,但实际 SQL 只按 key 筛选;若要按等待时长筛选,还需按目标版本的字段语义添加条件。

结果中的 STATE = LockWaiting、WAITING_START_TIME、SESSION_ID、ALL_SQL_DIGESTS 可帮助定位应用请求。由于采用 LEFT JOIN,事务已经结束时,关联列可能为 NULL;这不等于等待表已经损坏。

从一个被阻塞事务向持锁者追踪

已知等待事务的 start_ts(也是事务 ID)时,可以沿 current_holding_trx_id 找当前持锁者:

SELECT l.`key`, trx.*,
       TIDB_DECODE_SQL_DIGESTS(trx.all_sql_digests) AS sqls
FROM information_schema.data_lock_waits AS l
JOIN information_schema.cluster_tidb_trx AS trx
  ON l.current_holding_trx_id = trx.id
WHERE l.trx_id = 426831965449355272
LIMIT 100;

本例保留原文事务 ID,并增加展示上限。TIDB_DECODE_SQL_DIGESTS 把 ALL_SQL_DIGESTS 中的摘要集合转换成归一化 SQL,便于看到诸如 BEGIN、SELECT FOR UPDATE、UPDATE 的执行轨迹。它不会恢复被移除的参数。若不知道 start_ts,可结合事务表和 PROCESSLIST / CLUSTER_PROCESSLIST 寻找线索。若会话等待的是 schema 更改,应另查元数据锁,不要直接套用行锁结论。

乐观事务中的读写冲突

事务取得单调递增的 start_ts,读取目标 key 时需要找到 commit_ts < start_ts 的最新版本。若目标 key 上还留有锁,读取方可能无法判断另一事务是否已提交完成,需要处理锁并重试。原文的时序是:Txn0 已完成 Prewrite,正在 Commit;Txn1 发起读取时看见尚未清理的锁,因此产生读写冲突。

监控侧可以观察 TiDB Grafana 的 KV Errors → Lock Resolve OPS 中 not_expired / resolve,以及 KV Backoff OPS 的相关锁退避项。not_expired 指锁还未到期,resolve 指尝试解析/清理锁。原文监控说明写作 tikvLockFast,日志例写作 txnLockFast;具体标签与面板名称要以目标版本监控定义为准,不应混作稳定 API。

原 TiDB 日志例包含 txnStartTS、region_id、backoff_ms:255 与 backoff_types:[txnLockFast,txnLockFast]。它们分别指向读取事务时间戳、Region、退避耗时(毫秒)以及重试类别。原文 TiKV 日志则出现 locked、primary_lock、lock_version、key、lock_ttl、txn_size:锁版本对应持锁事务 start_ts,txn_size 指该事务在该 Region 的 key 数量,可影响清锁方式。此处可能涉及未提交的乐观锁,也可能是悲观事务 Prewrite 后的锁。

该文档记录的读写冲突退避值为初始 10 ms、单次最大 3000 ms、累计最大 20000 ms。这些是源文描述的实现参数,不能当作所有 TiDB 版本和配置的重试承诺。需要把冲突指标、事务耗时和业务访问模式合在一起看。

为了把 key 与业务表关联,原文给出 TiDB Control 的 decoder 用法;下方是保留转义字符的示例输入,不是实际目标数据:

./tidb-ctl decoder "t\x00\x00\x00\x00\x00\x00\x00\x1c_r\x00\x00\x00\x00\x00\x00\x00\xfa"

原例解码为 format: table_row、table_id: -9223372036854775780、row_id: -9223372036854775558。编码格式和工具兼容性必须按实际版本核对;输入应按工具文档传递,不能把未可信日志内容当成 shell 代码。

KeyIsLocked 与锁已被清除

KeyIsLocked:Prewrite 首先检查写写冲突,然后检查 key 是否已被另一个事务上锁。原文指出这一类情况主要通过监控观察,后台会退避重试,相关项为 resolve 和 txnLock。少量重试不一定需要干预,大量持续重试应从业务层寻找竞争原因,也可评估悲观模式。文档给出的该类退避初值为 100 ms、单次最大 3000 ms,同样有版本边界。

TxnLockNotFound:原文描述的是事务提交过慢,锁的 TTL 已过期,被其他事务回滚;提交时因此找不到锁。可在 TiDB 的 commit failed 日志或 TiKV 的 Txn(Mvcc(TxnLockNotFound)) 中发现线索。保留出错事务的 start_ts 与 commit_ts,并用匹配集群版本的工具解出时间:

tiup ctl:v<CLUSTER_VERSION> pd tso [start_ts]
tiup ctl:v<CLUSTER_VERSION> pd tso [commit_ts]

尖括号、方括号是原文占位符,不应原样粘贴执行;< 在 shell 中还可能被解释为重定向。工具版本应与集群匹配,TiUP 可能下载组件。比较解码后的时间并结合当时锁 TTL,检查写入或提交是否变慢,不能直接把两个原始 TSO 数相减当作毫秒。

自动事务重试的行为取决于相关设置及显式/隐式事务区别。若由应用捕获并重试,应先确保重试整个事务的业务语义正确、外部副作用幂等,并设置次数与退避边界。这是对原文“进行重试”的编辑补充;不能无限重试已经发过消息或扣过款的流程。

悲观事务的常见报错

按该版本文档,即使配置了悲观模式,autocommit 事务仍可能先以乐观方式尝试提交,发生冲突重试时再转悲观模式。因此,不能只看全局模式名称就断言实际执行路径。

现象 含义与处理方向
读写冲突 可沿前述日志、监控和退避路径分析,悲观模式并不消除所有读写锁竞争。
pessimistic lock retry limit reached 严重竞争或加锁期间出现 write conflict,语句重试达到 pessimistic-txn.max-retry-count 的上限。频繁发生时先调整热点业务;不要把每次重试日志都视为独立故障。
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction 语句等待锁超过 innodb_lock_wait_timeout。这是 SQL 语句层面的最大等锁时间,应查是谁持锁、为何迟迟不能释放。
TTL manager has timed out, pessimistic locks may expire, please commit or rollback this transaction 悲观事务持续太久,TTL 管理达到上限,后续提交可能失败。该文档描述默认上限一小时,配置为 performance.max-txn-ttl,还受 GC 等限制;实际值需按运行版本配置核查。
Deadlock found when trying to get lock; try restarting transaction(1213) 构成等待环,系统需中止其中一个事务打破死锁。查询死锁表还原顺序,频繁发生时调整业务访问顺序和事务范围。

对于同一行的高并发加锁,原文建议评估 tidb_pessimistic_txn_fair_locking(v7.0.0 起引入)。公平锁可能减少某些竞争问题,但存在吞吐下降、平均延迟上升的代价;该文档说明新部署集群默认启用,并不说明所有升级集群和现有会话都拥有相同值。

对于 TTL 超时,优先考虑把大事务拆成业务上可接受的小事务,并缩短事务内的非数据库等待;只有理解原子性和失败恢复要求后,才适度调整参数。不能仅通过延长 TTL、等锁时间或直接终止会话掩盖持续业务竞争。

诊断结果如何形成可用结论

保留采样时间、等待者与持锁者 ID、key、SQL 摘要、监控走势和对应业务操作,把“看到了什么”与“推测为什么”分开。一次 JOIN 结果不足以证明完整等待链;一次重试也不足以说明数据库不可用。本文重排了官方中文文档结构、在主文整理关键字段,附录保留完整历史输出,并明确标注 SQL 和安全使用方面的编辑补充,没有制造测试结果。

静态审查范围包括只读 SQL、输入引用、系统表敏感信息、参数与日志的版本依赖以及重试幂等性。未连接数据库、执行 decoder/TiUP、修改参数或重放事务;未发现示例含硬编码凭据不等于无漏洞。

来源与许可:PingCAP 及 TiDB 文档贡献者;正文基于官方原文和v8.5 文档源文件整理。仓库的LICENSE为 CC BY-SA 3.0;本整理稿按相同许可提供,修改说明如上,内容按“AS IS”提供、无保证。配图为未完纪自绘,按 CC BY-SA 3.0 提供。

原教程的完整诊断输出与图

以下保留上游历史SQL、终端及日志示例,参数、IP、ID和监控曲线均为原文演示数据,不是新的运行结果。正文的单引号和LIMIT修订另有说明。

原文诊断代码/输出 1

select * from information_schema.deadlocks;

原文诊断代码/输出 2

+-------------+----------------------------+-----------+--------------------+------------------------------------------------------------------+-----------------------------------------+----------------------------------------+----------------------------------------------------------------------------------------------------+--------------------+
| DEADLOCK_ID | OCCUR_TIME                 | RETRYABLE | TRY_LOCK_TRX_ID    | CURRENT_SQL_DIGEST                                               | CURRENT_SQL_DIGEST_TEXT                 | KEY                                    | KEY_INFO                                                                                           | TRX_HOLDING_LOCK   |
+-------------+----------------------------+-----------+--------------------+------------------------------------------------------------------+-----------------------------------------+----------------------------------------+----------------------------------------------------------------------------------------------------+--------------------+
|           1 | 2021-08-05 11:09:03.230341 |         0 | 426812829645406216 | 22230766411edb40f27a68dadefc63c6c6970d5827f1e5e22fc97be2c4d8350d | update `t` set `v` = ? where `id` = ? ; | 7480000000000000355F728000000000000002 | {"db_id":1,"db_name":"test","table_id":53,"table_name":"t","handle_type":"int","handle_value":"2"} | 426812829645406217 |
|           1 | 2021-08-05 11:09:03.230341 |         0 | 426812829645406217 | 22230766411edb40f27a68dadefc63c6c6970d5827f1e5e22fc97be2c4d8350d | update `t` set `v` = ? where `id` = ? ; | 7480000000000000355F728000000000000001 | {"db_id":1,"db_name":"test","table_id":53,"table_name":"t","handle_type":"int","handle_value":"1"} | 426812829645406216 |
+-------------+----------------------------+-----------+--------------------+------------------------------------------------------------------+-----------------------------------------+----------------------------------------+----------------------------------------------------------------------------------------------------+--------------------+

原文诊断代码/输出 3

select `key`, count(*) as `count` from information_schema.data_lock_waits group by `key` order by `count` desc;

原文诊断代码/输出 4

+----------------------------------------+-------+
| key                                    | count |
+----------------------------------------+-------+
| 7480000000000000415F728000000000000001 |     2 |
| 7480000000000000415F728000000000000002 |     1 |
+----------------------------------------+-------+

原文诊断代码/输出 5

select trx.* from information_schema.data_lock_waits as l left join information_schema.cluster_tidb_trx as trx on l.trx_id = trx.id where l.key = "7480000000000000415F728000000000000001"\G

原文诊断代码/输出 6

*************************** 1. row ***************************
               INSTANCE: 127.0.0.1:10080
                     ID: 426831815660273668
             START_TIME: 2021-08-06 07:16:00.081000
     CURRENT_SQL_DIGEST: 06da614b93e62713bd282d4685fc5b88d688337f36e88fe55871726ce0eb80d7
CURRENT_SQL_DIGEST_TEXT: update `t` set `v` = `v` + ? where `id` = ? ;
                  STATE: LockWaiting
     WAITING_START_TIME: 2021-08-06 07:16:00.087720
        MEM_BUFFER_KEYS: 0
       MEM_BUFFER_BYTES: 0
             SESSION_ID: 77
                   USER: root
                     DB: test
        ALL_SQL_DIGESTS: ["0fdc781f19da1c6078c9de7eadef8a307889c001e05f107847bee4cfc8f3cdf3","06da614b93e62713bd282d4685fc5b88d688337f36e88fe55871726ce0eb80d7"]
*************************** 2. row ***************************
               INSTANCE: 127.0.0.1:10080
                     ID: 426831818019569665
             START_TIME: 2021-08-06 07:16:09.081000
     CURRENT_SQL_DIGEST: 06da614b93e62713bd282d4685fc5b88d688337f36e88fe55871726ce0eb80d7
CURRENT_SQL_DIGEST_TEXT: update `t` set `v` = `v` + ? where `id` = ? ;
                  STATE: LockWaiting
     WAITING_START_TIME: 2021-08-06 07:16:09.290271
        MEM_BUFFER_KEYS: 0
       MEM_BUFFER_BYTES: 0
             SESSION_ID: 75
                   USER: root
                     DB: test
        ALL_SQL_DIGESTS: ["0fdc781f19da1c6078c9de7eadef8a307889c001e05f107847bee4cfc8f3cdf3","06da614b93e62713bd282d4685fc5b88d688337f36e88fe55871726ce0eb80d7"]
2 rows in set (0.00 sec)

原文诊断代码/输出 7

select l.key, trx.*, tidb_decode_sql_digests(trx.all_sql_digests) as sqls from information_schema.data_lock_waits as l join information_schema.cluster_tidb_trx as trx on l.current_holding_trx_id = trx.id where l.trx_id = 426831965449355272\G

原文诊断代码/输出 8

*************************** 1. row ***************************
                    key: 74800000000000004D5F728000000000000001
               INSTANCE: 127.0.0.1:10080
                     ID: 426832040186609668
             START_TIME: 2021-08-06 07:30:16.581000
     CURRENT_SQL_DIGEST: 06da614b93e62713bd282d4685fc5b88d688337f36e88fe55871726ce0eb80d7
CURRENT_SQL_DIGEST_TEXT: update `t` set `v` = `v` + ? where `id` = ? ;
                  STATE: LockWaiting
     WAITING_START_TIME: 2021-08-06 07:30:16.592763
        MEM_BUFFER_KEYS: 1
       MEM_BUFFER_BYTES: 19
             SESSION_ID: 113
                   USER: root
                     DB: test
        ALL_SQL_DIGESTS: ["0fdc781f19da1c6078c9de7eadef8a307889c001e05f107847bee4cfc8f3cdf3","a4e28cc182bdd18288e2a34180499b9404cd0ba07e3cc34b6b3be7b7c2de7fe9","06da614b93e62713bd282d4685fc5b88d688337f36e88fe55871726ce0eb80d7"]
                   sqls: ["begin ;","select * from `t` where `id` = ? for update ;","update `t` set `v` = `v` + ? where `id` = ? ;"]
1 row in set (0.01 sec)

原文诊断代码/输出 9

        [INFO] [coprocessor.go:743] ["[TIME_COP_PROCESS] resp_time:406.038899ms txnStartTS:416643508703592451 region_id:8297 store_addr:10.8.1.208:20160 backoff_ms:255 backoff_types:[txnLockFast,txnLockFast] kv_process_ms:333 scan_total_write:0 scan_processed_write:0 scan_total_data:0 scan_processed_data:0 scan_total_lock:0 scan_processed_lock:0"]
        

原文诊断代码/输出 10

    [ERROR] [endpoint.rs:454] [error-response] [err=""locked primary_lock:7480000000000004D35F6980000000000000010380000000004C788E0380000000004C0748 lock_version: 411402933858205712 key: 7480000000000004D35F7280000000004C0748 lock_ttl: 3008 txn_size: 1""]
    

原文诊断代码/输出 11

    ./tidb-ctl decoder "t\x00\x00\x00\x00\x00\x00\x00\x1c_r\x00\x00\x00\x00\x00\x00\x00\xfa"
    format: table_row
    table_id: -9223372036854775780
    row_id: -9223372036854775558
    

原文诊断代码/输出 12

    [WARN] [session.go:446] ["commit failed"] [conn=149370] ["finished txn"="Txn{state=invalid}"] [error="[kv:6]Error: KV error safe to retry tikv restarts txn: Txn(Mvcc(TxnLockNotFound{ start_ts: 412720515987275779, commit_ts: 412720519984971777, key: [116, 128, 0, 0, 0, 0, 1, 111, 16, 95, 114, 128, 0, 0, 0, 0, 0, 0, 2] })) [try again later]"]
    

原文诊断代码/输出 13

    Error: KV error safe to retry restarts txn: Txn(Mvcc(TxnLockNotFound)) [ERROR [Kv.rs:708] ["KvService::batch_raft send response fail"] [err=RemoteStoped]
    

原文诊断代码/输出 14

    tiup ctl:v<CLUSTER_VERSION> pd tso [start_ts]
    tiup ctl:v<CLUSTER_VERSION> pd tso [commit_ts]
    

原文诊断代码/输出 15

err="pessimistic lock retry limit reached"

原文诊断代码/输出 16

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

原文诊断代码/输出 17

TTL manager has timed out, pessimistic locks may expire, please commit or rollback this transaction

原文诊断代码/输出 18

[err="[executor:1213]Deadlock found when trying to get lock; try restarting transaction"]
Txn0的Prewrite已完成、Commit尚未完成时,Txn1读请求遇到锁的时序。
PingCAP及TiDB文档贡献者原图:Txn0的Prewrite已完成、Commit尚未完成时,Txn1读请求遇到锁的时序。 CC BY-SA 3.0,未修改。
读写冲突案例的KV Backoff OPS,红线为tikvLockFast。
PingCAP及TiDB文档贡献者原图:读写冲突案例的KV Backoff OPS,红线为tikvLockFast。 CC BY-SA 3.0,未修改。
Lock Resolve OPS,绿色resolve曲线显示解析锁的活动;原文在两处引用同一图。
PingCAP及TiDB文档贡献者原图:Lock Resolve OPS,绿色resolve曲线显示解析锁的活动;原文在两处引用同一图。 CC BY-SA 3.0,未修改。
KeyIsLocked案例的KV Backoff OPS,紫线为txnLock。
PingCAP及TiDB文档贡献者原图:KeyIsLocked案例的KV Backoff OPS,紫线为txnLock。 CC BY-SA 3.0,未修改。
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容