在这里插入图片描述
你的 Java 日志里突然疯狂报错:
java.sql.SQLException: Lock wait timeout exceeded; try restarting transaction
第一反应:
很多人的第一反应是执行 SHOW PROCESSLIST;
结果看到一堆 Sleep 状态的连接,或者是几百个 UPDATE 正在 Updating。你看着这堆列表,完全不知道是谁阻塞了谁。
痛点:
SHOW PROCESSLIST 只能看到谁在干活,或者谁在睡觉,但它看不出谁拿着锁不放
神器降临:
MySQL 5.7 (及 8.0+) 自带的 sys 库中,有一个被严重低估的视图:sys.innodb_lock_waits。它能像侦探一样,直接告诉你:“线程 A 正在等待锁,而锁被线程 B 拿着,线程 B 正在执行(或执行过)这条 SQL…”


1. 核心原理:不再盲猜

在 MySQL 内部,锁的信息分布在 information_schema 的三张表里(INNODB_TRX, INNODB_LOCKS, INNODB_LOCK_WAITS)。

  • TRX: 当前运行的事务。
  • LOCKS: 锁的具体信息。
  • WAITS: 谁在等谁。

要把这三张表关联起来看懂,需要写很复杂的 JOIN 语句。而 sys.innodb_lock_waits 视图就是官方帮我们写好的这个复杂 JOIN。它直接展示了 Blocker (阻塞者)Waiter (等待者) 的关系。


2. 实战演练:一键抓捕

请把这条 SQL 加入你的收藏夹或常用的数据库管理工具(Navicat/DBeaver)的 Snippet 中。

神级查询 SQL:
SELECT 
    -- 1. 等待的时间 (以此排序,看谁等最久)
    wait_age AS '等待时长',
    
    -- 2. 受害者 (谁被锁住了?)
    waiting_pid AS '受害者ID',
    waiting_query AS '受害者想执行的SQL',
    
    -- 3. 罪魁祸首 (谁拿着锁?)
    blocking_pid AS '肇事者ID',
    blocking_trx_started AS '肇事事务开始时间',
    
    -- 4. 关键证据 (肇事者最后执行的 SQL)
    -- 注意:如果肇事者正在 Sleep,这里显示的是它最后执行的那条语句
    blocking_query AS '肇事者最后执行的SQL',
    
    -- 5. 锁的类型
    locked_table AS '被锁表名',
    locked_type AS '锁类型'
    
FROM sys.innodb_lock_waits
ORDER BY wait_age DESC;

执行结果示例:
等待时长 受害者ID 受害者SQL 肇事者ID 肇事者最后执行的SQL
00:00:45 102 UPDATE stock SET num = num -1… 88 NULL (或者某个 SELECT)

分析:

  • ID 为 102 的线程已经被卡了 45 秒。
  • 凶手是 ID 为 88 的线程。
  • 下一步行动: 直接 KILL 88; 即可解救众生。

3. 三大实战场景

场景一:未提交的事务 (The Zombie Transaction)

现象: blocking_query 显示为 NULL 或者一条很简单且已经执行完的 SQL,但 blocking_pid 的状态是 Sleep
原因: 这是最坑的场景。通常是开发者在本地终端连测试库,执行了 BEGIN; UPDATE ...; 然后忘记 COMMIT,就去吃饭了。或者代码中开启了事务,但因为某个逻辑 Bug(如死循环、外部 HTTP 调用超时)导致迟迟没有 Commit。
排查: sys.innodb_lock_waits 能一眼看出这个 Sleep 的线程 88 拿着锁不放。

场景二:间隙锁 (Gap Lock) 误伤

现象: 你明明 UPDATE 是一行不存在的数据,或者 UPDATE 不同的行,却依然被锁。
原因: 在 Repeatable Read 隔离级别下,MySQL 会使用间隙锁。
比如 DELETE FROM users WHERE id > 100,即使目前只有 id=101 的数据,MySQL 也会锁住 (100, +∞) 的范围。此时你插入 id=102 就会被阻塞。
排查: 通过视图可以看到 locked_mode 包含 Gap 字样。

场景三:大事务导致的批量阻塞

现象: 一个后台定时任务正在删除 100 万条过期日志(DELETE FROM logs ...),导致前台所有写入操作全部卡死。
排查: 视图中会显示同一个 blocking_pid 对应了几十个 waiting_pid。这意味着一个大事务阻塞了全世界。
解决: 杀掉大事务,将其拆分为 DELETE + LIMIT 的小事务执行。


4. 进阶:如果 sys 库也没装怎么办?

在某些极度精简的云数据库环境,可能连 sys 库都没有。此时你需要回归原始手段:

SHOW ENGINE INNODB STATUS;

在输出的大段文本中,找到 LATEST DETECTED DEADLOCK (如果是死锁) 或 TRANSACTIONS 章节。
你会看到类似 MySQL thread id 88, OS thread handle ... 以及 TRX HAS BEEN WAITING 10 SEC 的描述。虽然晦涩,但也能找到线索。


5. 总结

不要等到数据库报警了才去 Google 怎么查锁。

  1. 预防: 避免长事务,大批处理用 LIMIT 分批。
  2. 监控: 监控长事务(Long Transaction)。
  3. 急救: 记住 SELECT * FROM sys.innodb_lock_waits。找到 blocking_pid,确认无误后,KILL 之。
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐