information_schema.innodb_trx、innodb_locks、innodb_lock_waits 是 mysql 5.7 分析 innodb 事务锁状态最直接有效的三张表,可实时查看活跃事务、锁持有与等待关系,无需开启性能模式。

INFORMATION_SCHEMA.INNODB_TRX、INNODB_LOCKS、INNODB_LOCK_WAITS 这三张表是 MySQL 5.7 中分析 InnoDB 事务锁状态最直接有效的入口。它们不依赖外部工具,也不需要开启性能模式,只要权限足够就能查到实时锁关系。
查当前活跃事务和锁等待链
先看谁在跑、谁在等、谁在堵:SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX 能看到每个事务的 trx_id、trx_state(如 LOCK WAIT)、trx_query(正在执行的 SQL)、trx_rows_locked(锁了多少行)。重点关注 trx_state = 'LOCK WAIT' 的记录,说明它卡住了。
再结合 INNODB_LOCK_WAITS 查等待关系:SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS 会返回两列关键 ID:requesting_trx_id(等锁的事务)和 blocking_trx_id(持锁的事务)。把这两个 ID 去 INNODB_TRX 里一查,就能串起完整的阻塞链。
-
INNODB_LOCK_WAITS在 5.7 中是可靠的,但注意它只记录“正在等待”的关系,不包含已释放或未触发等待的锁 - 如果
INNODB_LOCK_WAITS返回空,不代表没锁争用——可能只是还没走到等待阶段(比如两个事务同时尝试加互斥锁,其中一个被立即拒绝并报错) - 事务长时间处于
LOCK WAIT状态,大概率是持有锁的事务没提交或没回滚,要顺藤摸瓜查trx_started时间
看具体锁类型和锁定位置
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS 显示当前所有被持有的锁,每行代表一个锁实例,字段包括:lock_trx_id(哪个事务加的)、lock_mode(如 X,REC_NOT_GAP 或 X,GAP)、lock_type(RECORD 或 TABLE)、lock_data(锁住的具体值,对行锁是主键值或唯一索引值)。
注意:lock_data 不一定总是可读——如果锁在二级索引上且该索引非唯一,lock_data 可能只显示部分字段值;如果是 GAP 锁,lock_data 通常为空或显示范围边界(如 1,5 表示间隙 (1,5))。
-
lock_mode中的REC_NOT_GAP表示纯记录锁,GAP表示间隙锁,NEXT-KEY(实际显示为X或S加REC_NOT_GAP和GAP组合)表示临键锁 - 同一事务可能在多个索引上持不同类型的锁,比如在聚簇索引上加
X,REC_NOT_GAP,又在二级索引上加X,GAP - 如果
INNODB_LOCKS里某行的lock_trx_id在INNODB_TRX中查不到,说明该事务已结束但锁尚未完全清理(极少见,多见于崩溃恢复后)
配合 SHOW ENGINE INNODB STATUS 定位死锁细节
SHOW ENGINE INNODB STATUS\G 输出中 LATEST DETECTED DEADLOCK 段落是死锁分析的黄金信息源。它会明确写出两个(或多个)事务各自持有哪些锁、等待哪些锁,并标注哪一方被回滚。
但要注意:这个命令只保留最近一次死锁记录,且默认不开启全量锁输出。若需更详细上下文,得提前设置:SET GLOBAL innodb_print_all_deadlocks = ON(写入错误日志),以及确保 innodb_status_output_locks 为 ON(让 SHOW ENGINE 包含锁详情)。
- 死锁日志里出现
*** WE ROLL BACK TRANSACTION后面的trx_id是被选为牺牲者的事务,不是随机选的——InnoDB 会挑 undo log 量小、代价低的那个 - 如果死锁日志里
Locks held列为空,说明该事务还没来得及加锁就卡在了等待队列里(比如等待表级锁或元数据锁) -
SHOW ENGINE INNODB STATUS的其他部分(如TRANSACTIONS)也能看到当前所有事务的锁摘要,但不如三张INNODB_*表精确
容易忽略的锁视角:隐式锁与索引结构依赖
InnoDB 的很多锁是隐式的,不会出现在 INNODB_LOCKS 里。典型例子:唯一索引上的 duplicate key 检查会加 LOCK_ORDINARY(Next-Key),但这条锁只在冲突发生时才真正生成,平时查不到;还有外键约束检查触发的 GAP 锁,也只在 INSERT/UPDATE 涉及外键列时临时存在。
更关键的是:锁对象永远绑定在索引上。没有索引的 WHERE 条件会走全表扫描,这时锁会落在聚簇索引的每一行上(甚至整张表);而有索引但没命中(比如用函数包装字段),同样退化为全扫描加锁。所以 EXPLAIN 必须和锁分析一起看。
- 即使 SQL 看似只改一行,如果
WHERE条件没走索引,trx_rows_locked可能是几万甚至几十万 -
SELECT ... FOR UPDATE在 RC 隔离级别下只锁命中的行(REC_NOT_GAP),但在 RR 下会升级为 Next-Key 锁,连带锁住间隙——这是幻读防控的代价,也是很多“莫名卡住”的根源 - 事务里混用
SELECT和UPDATE时,前者可能不加锁(RC 下普通读),后者却因索引选择不同而锁住大片范围,这种不对称性最容易引发误判
真正难的不是查出锁在哪,而是理解为什么锁在那里。每一条 lock_mode 都对应着隔离级别、索引结构、SQL 写法三者的共同作用结果。盯着 INNODB_LOCKS.lock_data 看数值,不如回到业务 SQL,用 EXPLAIN 确认它到底扫了哪些索引、走了什么范围。











