登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  数据库 >  MySQL

MySQL 死锁日志里的锁类型和索引名怎么对应到 SQL

来源:17golang原创

时间:2026-09-08 07:13:03 261浏览 收藏

MySQL 死锁日志里的 index 不是 SQL 文本中的表别名,lock mode 也不是“某条 SQL 的类型”。正确的对应方式是:先用事务 ID 找到正在执行的语句,再用表名和索引名判断 InnoDB 锁住了哪棵索引,最后用 lock_data 对照 WHERE 条件和主键值。这样才能知道是两条 SQL 访问顺序相反,还是范围锁把相邻记录也纳入了等待。

看到 RECORD LOCKS ... index PRIMARY 时,先把它理解为“某张表的聚簇索引记录锁”;看到二级索引名时,要同时考虑该索引记录后附带的主键值。死锁诊断的重点不是逐字翻译日志,而是还原“谁持有什么、谁还要什么”。
要点速览
  • SHOW ENGINE INNODB STATUS 主要看最近一次死锁;频繁问题可打开 innodb_print_all_deadlocks 记录全部事件。
  • INDEX_NAME 指向锁所在索引,LOCK_TYPE 区分 TABLE/RECORD,LOCK_STATUS 区分 GRANTED/WAITING。
  • 二级索引锁的记录值通常还带主键;不要只凭索引名猜 SQL,必须结合事务语句、索引定义和执行条件。

先把日志字段翻译成锁对象

InnoDB 一行死锁记录通常同时出现表、索引、锁模式和记录值。表名回答“锁在哪张表”,索引名回答“通过哪棵索引定位到锁记录”,锁类型和状态回答“这是表级资源还是记录级资源、当前已持有还是正在等待”。例如 index PRIMARY 表示聚簇索引;index idx_account_status 则表示访问路径落在二级索引上。

字段怎么理解回到 SQL 时看什么
TABLE / RECORD表级或记录级锁是否由 DDL、LOCK TABLES 或行修改触发
PRIMARY / 二级索引名锁所在的索引索引列顺序、是否覆盖 WHERE 条件
S、X、IS、IX、GAP共享、排他、意向或间隙相关模式SELECT 加锁方式、UPDATE/DELETE 和范围条件
GRANTED / WAITING已持有或正在等待把等待资源连到另一事务持有的同一资源
LOCK_DATA索引记录值或间隙标识主键、二级索引列和 WHERE 的具体值
MySQL InnoDB 死锁诊断框图将事务、SQL、表、索引、记录和锁状态对应起来
图1:把事务和 SQL 放在请求边界,把表、索引、记录与锁状态放在 InnoDB 资源边界,日志字段才能对应到同一个锁对象。

用最近一次死锁记录锁定两条 SQL

先执行下面的语句。它只展示最近一次 InnoDB 用户事务死锁,输出中的 TRANSACTIONWAITING FOR THIS LOCKHOLDS THE LOCK(S)WE ROLL BACK TRANSACTION 是诊断主线。

-- 读取最近一次 InnoDB 死锁摘要,不修改事务数据
SHOW ENGINE INNODB STATUS\G

把每个事务分成两列记录:已持有的资源、正在等待的资源。若事务 A 持有 orders.PRIMARY 的某条记录,同时等待 payments.PRIMARY;事务 B 的方向相反,就已经得到典型的交叉等待。日志里的 SQL 是当时正在执行的语句,但它不一定包含完整业务参数,所以还要用连接线程、应用日志或请求 ID补全实际值。

如果现场经常发生,临时打开全部死锁记录:

-- 仅在排查期打开,便于从错误日志收集每一次死锁
SET GLOBAL innodb_print_all_deadlocks = ON;

-- 排查结束后关闭,避免长期增加日志噪声
SET GLOBAL innodb_print_all_deadlocks = OFF;

用 data_locks 把索引名和记录值补齐

死锁发生前后,Performance Schema 的锁表更适合看结构化字段。data_locks 同时包含已授予和等待中的数据锁;INDEX_NAMELOCK_MODELOCK_STATUSLOCK_DATA 正好对应日志中最容易误读的部分。

-- 先按事务、表和索引缩小范围,再看记录与状态
SELECT ENGINE_TRANSACTION_ID,
       OBJECT_SCHEMA,
       OBJECT_NAME,
       INDEX_NAME,
       LOCK_TYPE,
       LOCK_MODE,
       LOCK_STATUS,
       LOCK_DATA
FROM performance_schema.data_locks
WHERE OBJECT_SCHEMA = 'shop'
  AND OBJECT_NAME IN ('orders', 'payments')
ORDER BY ENGINE_TRANSACTION_ID, OBJECT_NAME, INDEX_NAME;

-- 查看谁持有锁、谁在等待该锁
SELECT requesting_engine_transaction_id,
       blocking_engine_transaction_id,
       requesting_engine_lock_id,
       blocking_engine_lock_id
FROM performance_schema.data_lock_waits;

这里有三个关键边界。第一,表级意向锁的 INDEX_NAMELOCK_DATA 可能为空,不要把它当成缺失索引。第二,二级索引记录的 LOCK_DATA 通常包含二级索引值并附带主键,用主键回表确认具体行。第三,supremum pseudo-record 表示索引页上的特殊上界记录,常见于范围或间隙相关诊断,不能当成业务主键。

MySQL data_locks 查询结构图展示表名、索引名、锁模式、记录值与等待关系如何回到 SQL 条件
图2:结构化锁表把表、索引、模式、状态和记录值连到等待关系,之后再回到 SQL 的 WHERE 与事务顺序。

从索引记录回到 SQL,修正访问顺序

拿到索引名后先查索引定义,而不是直接改隔离级别。假设日志指向 idx_account_status,就检查它的列顺序是否覆盖了业务条件:

-- 查看索引列顺序,确认日志中的索引如何匹配条件
SHOW CREATE TABLE orders\G

-- 只取需要加锁的行,保持两个事务使用同一访问顺序
START TRANSACTION;
SELECT id, status
FROM orders
WHERE account_id = 42 AND status = 'pending'
FOR UPDATE;
-- 业务更新完成后再提交,避免长时间占锁
COMMIT;

常见修复是让多个事务按相同顺序访问多张表或多个范围、缩短事务持续时间、为 UPDATESELECT ... FOR UPDATE 的条件建立合适索引,并在应用层对死锁回滚做有限次数重试。隔离级别会影响读操作的可见性,但不能把“死锁只靠调低隔离级别解决”当成结论;真正要复查的是锁定范围、索引路径和事务顺序。

常见问题

日志里的索引名就是 SQL 使用的唯一索引吗?

不一定。它表示 InnoDB 记录锁所在的索引;没有显式主键时也可能看到内部聚簇索引。应结合 SHOW CREATE TABLE 和执行计划确认访问路径。

为什么 LOCK_DATA 会是 NULL?

表级锁本来没有记录值;记录页不在缓冲池时,InnoDB 也可能不为诊断重新读盘。NULL 不能单独证明没有锁住具体行。

遇到死锁只要把 innodb_lock_wait_timeout 调大吗?

不建议。死锁检测开启时 InnoDB 会回滚一个事务,应用仍应捕获回滚并有限重试;应先修正事务顺序和锁定范围。

相关依据

字段含义可查阅 MySQL 8.4 的data_locks 表说明Performance Schema 锁表概览;死锁诊断与处理可查阅InnoDB DeadlocksHow to Minimize and Handle Deadlocks。生产环境修改全局变量前,应结合日志保留策略和权限范围安排排查窗口。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>