要快速定位谁在持有行锁,先查innodb_trx中状态为'lock wait'的事务,再通过innodb_lock_waits找到其blocking_trx_id,最后在innodb_trx中查该id对应的trx_mysql_thread_id即可定位并kill。

怎么快速定位谁在 hold 住那把行锁
MySQL 的 Lock wait timeout exceeded 错误不是锁本身超时,而是你的事务等了太久、主动被干掉了。真正卡住你的,是另一个还没提交(或没回滚)的事务正拿着你要的行锁。
关键不是查“谁超时”,而是查“谁在锁着”。直接看 information_schema.INNODB_TRX 和 INNODB_LOCK_WAITS 是最准的路径:
-
SELECT * FROM information_schema.INNODB_TRX WHERE trx_state = 'LOCK WAIT'—— 找出正在等锁的事务,记下它的trx_id - 用这个
trx_id去INNODB_LOCK_WAITS查blocking_trx_id,就知道谁在挡路 - 再用那个
blocking_trx_id去INNODB_TRX查trx_mysql_thread_id,就能KILL它
注意:5.7+ 默认开启 innodb_status_output_locks,但生产环境别依赖 SHOW ENGINE INNODB STATUS——信息杂、不实时、还可能被截断。
为什么 SELECT ... FOR UPDATE 也容易触发锁等待
很多人以为只有 UPDATE 或 DELETE 才加行锁,其实 SELECT ... FOR UPDATE 和 SELECT ... LOCK IN SHARE MODE 同样会申请锁,而且默认走当前读(consistent read 不生效)。
常见踩坑点:
- 没加
WHERE条件,或者条件没走索引 → 锁全表(或整个二级索引),别人一更新就堵死 - 在事务里执行了
SELECT ... FOR UPDATE,但后面忘了COMMIT或ROLLBACK→ 锁一直挂着 - 用了
READ COMMITTED隔离级别,但锁还是不会提前释放:行锁只在事务结束时释放,跟隔离级别无关
验证是否走索引?用 EXPLAIN 看 type 是不是 range/ref,key 字段有没有值。
如何避免长事务拖垮整个库的并发
一个跑了 5 分钟的事务,哪怕只 update 一行,也会让所有想改同一行的请求排队等死。根本问题不在锁策略,而在事务生命周期失控。
实操建议:
- 业务代码里,
BEGIN和COMMIT/ROLLBACK必须成对出现,禁止跨函数、跨 HTTP 请求边界持锁 - 在应用层加事务超时控制(比如 Spring 的
@Transactional(timeout = 3)),比等 MySQL 报错更早止损 - 监控
INNODB_TRX.trx_started,告警超过 30 秒的活跃事务(SELECT * FROM information_schema.INNODB_TRX WHERE TIME_TO_SEC(NOW()) - TIME_TO_SEC(trx_started) > 30)
别指望靠调大 innodb_lock_wait_timeout 解决问题——它只是把报错延后,锁冲突还在那里。
为什么 SHOW PROCESSLIST 看不到锁信息
SHOW PROCESSLIST 只显示连接状态和正在执行的 SQL,完全不反映 InnoDB 层的锁关系。你看到一堆 Sleep 状态的线程,可能其中某个正握着 10 行记录的 X 锁,而其他 20 个线程全卡在 UPDATE 上动不了。
真正有用的组合是:
-
SELECT * FROM information_schema.INNODB_TRX—— 看谁在跑、跑了多久、在等什么锁 -
SELECT * FROM information_schema.INNODB_LOCKS(8.0 已移除,别用)→ 改用performance_schema.data_locks -
SELECT * FROM performance_schema.data_lock_waits(8.0+)或回退到INNODB_LOCK_WAITS(5.7)
MySQL 8.0 把锁信息全挪进 performance_schema,字段名和关联方式变了,直接照搬老脚本会查不到数据。










