真正该处理的是持锁未提交的事务,而非报错的等待线程;需联动查询innodb_trx、processlist和metadata_locks,重点定位trx_state='running'且trx_started超60秒的悬挂事务。

怎么快速定位谁在持锁不放
报错的事务只是“等死”的那个,真正该处理的是它前面那个迟迟没提交的事务。别只看 SHOW FULL PROCESSLIST——它只显示当前线程状态,未必反映真实锁持有者。
用这三条 SQL 联动查:
SELECT * FROM information_schema.INNODB_TRX WHERE trx_state = 'RUNNING' AND trx_started :先筛出运行超1分钟的活跃事务,重点关注 <code>trx_mysql_thread_id和trx_query-
SELECT * FROM information_schema.INNODB_LOCK_WAITS:查阻塞关系,拿到BLOCKING_TRX_ID -
SELECT ID, USER, HOST, COMMAND, TIME, STATE, INFO FROM information_schema.PROCESSLIST WHERE ID = ?:把上一步的BLOCKING_TRX_ID填进去,确认持锁线程的真实身份
如果 INNODB_LOCK_WAITS 查询结果为空,说明锁已释放,或已被 InnoDB 自动回滚为死锁(此时去 error log 查 Deadlock found)。
为什么加索引能直接缓解锁等待
没走索引的 WHERE 条件会让 InnoDB 升级为全表扫描,进而锁住大量无关行——锁范围越大,冲突概率越高,超时就越容易发生。
比如这条语句:
UPDATE device_data_message SET device_id = 45 WHERE monitor_id IN ( SELECT monitor_id FROM device_data_message WHERE pillar_id = 10010 );
执行计划里若出现 type: ALL,就是典型信号。立刻补索引:
ALTER TABLE device_data_message ADD INDEX idx_pillar_collect (pillar_id, collect_type);
注意:复合索引顺序不能乱,pillar_id 必须放在最左;如果查询还带 ORDER BY collect_type,这个顺序刚好能覆盖。
innodb_lock_wait_timeout 调大有用吗
基本没用,还危险。它不解决锁争抢,只延迟报错——用户等得更久,连接池可能先被耗尽,而真实瓶颈还在那儿。
几个关键事实:
- 该参数默认是 50 秒,改完对已有连接无效,只影响新会话
-
SET GLOBAL innodb_lock_wait_timeout = 120是全局生效,但重启后失效;写进配置文件需重启 MySQL - 事务内用
SET innodb_lock_wait_timeout = 120只作用于当前事务,但掩盖了持锁过久的问题
真正该盯的是 trx_started 时间戳:如果持锁事务的启动时间远早于当前时间(比如 >60 秒),大概率是应用层卡在 HTTP 调用、文件读写或未提交逻辑里。
遇到 Waiting for table metadata lock 怎么办
这不是行锁问题,是 DDL 阻塞。比如有人正在执行 ALTER TABLE,其他所有对该表的读写都会卡在元数据锁(MDL)上,报错信息里明确写着 Waiting for table metadata lock。
排查路径完全不同:
- 查
information_schema.PROCESSLIST,找STATE为Waiting for table metadata lock的线程,记下ID - 再查
SELECT * FROM information_schema.PROCESSLIST WHERE ID = ?,看它的INFO字段是不是ALTER TABLE ...或CREATE INDEX - 如果是,且该 DDL 执行时间异常长(比如 >5 分钟),优先考虑取消或改在低峰期执行;不要 KILL,否则可能留下中间状态
行锁和 MDL 锁的排查逻辑完全隔离,混在一起查只会浪费时间。











