应查information_schema.innodb_trx中trx_started早、trx_state='running'且trx_query为空的长事务,它是持有mdl读锁的元凶;需用kill(非kill query)终止其连接以释放锁。

Waiting for table metadata lock 不是表真被锁死了,而是 ALTER TABLE 或其他 DDL 在等一个长事务释放 MDL 读锁——你得先找到并干掉那个“赖着不走”的事务,而不是杀 DDL。
怎么快速定位正在拖后腿的长事务
别只看 SHOW PROCESSLIST,它显示的是等待者,不是持有者。真正管用的是查 INFORMATION_SCHEMA.INNODB_TRX:
-
TRX_STARTED时间远早于当前时间(比如 >30 秒)的事务,基本就是元凶 -
TRX_QUERY为空 ≠ 安全,可能只是执行了BEGIN就挂起了 -
TRX_STATE = 'RUNNING'不代表活跃,也可能是空闲等待提交 - 搭配
performance_schema.data_lock_waits(MySQL 8.0+)能看清谁在等谁,但注意INNODB_LOCK_WAITS已弃用
kill 前必须确认的三件事
直接 KILL <thread_id></thread_id> 很快,但后果要兜得住:
- 确认该线程不属于关键批处理流程(比如账务对账、日终结算)
- 检查
TRX_MYSQL_THREAD_ID对应的连接是否由应用层主动控制,还是连接池里“睡过头”的连接 - 优先用
KILL <thread_id></thread_id>(不是KILL QUERY),只有完整 kill 才能回滚事务、释放 MDL 锁
为什么不能只靠 innodb_lock_wait_timeout 解决问题
这个参数只管“等锁的人”,不管“持锁的人”:
- 设成 10 秒,只是让等待者报
Lock wait timeout exceeded,不影响长事务继续占着锁 - 它对已持有的行锁、间隙锁、MDL 锁完全无效
- 盲目调小(如 1 秒)会导致正常批量导入或报表查询频繁中断,掩盖真实瓶颈
- 真正该开的是
innodb_print_all_deadlocks = 1,把死锁上下文全记进错误日志
预防比抢救更关键的实操点
线上没人天天盯着 INNODB_TRX,靠监控和规范堵住源头才稳:
- 所有写操作必须显式包裹在
BEGIN/COMMIT块里,禁止跨 HTTP 请求延续事务上下文 - ORM 框架(如 Django、Spring)默认开启事务,得配
@Transactional(timeout = 5)或等价机制 - 批量更新拆成每 500–1000 行一批,中间
COMMIT,避免单事务锁太多行 -
SELECT ... FOR UPDATE必须走索引,否则会升级为全表扫描加锁——哪怕只改一行,也可能锁住整张表
INNODB_TRX 里、开了半小时还没 COMMIT 的事务。











