必须定位并终止“sleep但trx_state=running”的未提交业务连接,而非杀sql线程;查show processlist中system user状态、slave_sql_running_state及gtid差值,并用performance_schema.metadata_locks和innodb_trx联合确认持锁源。

从库SQL线程卡在 Waiting for table metadata lock,不是复制慢,是被持锁的连接堵死了——必须定位并终止那个“睡着但没提交”的业务连接,而不是杀SQL线程本身。
怎么确认真被MDL卡住了
别信 Seconds_Behind_Master,它可能为0但SQL线程已挂起。重点看三处:
-
SHOW PROCESSLIST中system user线程的State字段是否为Waiting for table metadata lock -
SHOW SLAVE STATUS\G里Slave_SQL_Running_State是否卡在这个状态,且Retrieved_Gtid_Set和Executed_Gtid_Set差一个 GTID(对应主库刚执行的ALTER TABLE) -
SELECT * FROM performance_schema.metadata_locks WHERE LOCK_STATUS = 'GRANTED' AND OBJECT_SCHEMA = 'your_db' AND OBJECT_NAME = 'your_table',确认是否有长时持有者
怎么找到真正持锁的连接
持锁者大概率是个 Command = 'Sleep'、Time 很大(比如 > 60)、但在 INNODB_TRX 里 trx_state = 'RUNNING' 且 trx_query IS NULL 的连接——这是事务没提交导致MDL一直挂着。
- 查可疑事务:
SELECT trx_id, trx_started, trx_state, trx_mysql_thread_id, trx_query FROM information_schema.INNODB_TRX WHERE TIMESTAMPDIFF(SECOND, trx_started, NOW()) > 120 - 用查到的
trx_mysql_thread_id去performance_schema.threads找对应THREAD_ID - 再查
performance_schema.metadata_locks,确认该线程是否对目标表持有LOCK_TYPE = 'SHARED_READ'或'SHARED_WRITE'
KILL要分两步,不能直接干掉SQL线程
直接 KILL CONNECTION 会触发完整回滚,undo量大时锁还在;更糟的是,KILL system user 的SQL线程会导致复制中断。
- 先执行
KILL QUERY thread_id(对Sleep连接虽无效,但能排除它正在跑长查询的可能) - 等10–20秒,再查
INNODB_TRX状态是否变化;若仍为RUNNING且trx_query IS NULL,再执行KILL CONNECTION thread_id - 绝对不要
KILLsystem user的线程,那是复制通道
为什么INSTANT或pt-osc也不能完全防住MDL
ALGORITHM=INSTANT 仍需获取 MDL_EXCLUSIVE 锁,照样会被未提交的 SELECT 或 UPDATE 阻塞;pt-online-schema-change 绕过原生DDL路径,但若从库上已有长事务或备份脚本(如 mysqldump --single-transaction 没加 --skip-lock-tables),照样卡住。
- 哪怕语法支持
INSTANT,只要表上有活跃事务(哪怕只是START TRANSACTION; SELECT * FROM t;),DDL依然卡在Waiting for table metadata lock - 从库禁止手动执行
FLUSH TABLES WITH READ LOCK;备份务必用--single-transaction+--skip-lock-tables - 主库
wait_timeout必须设为300(5分钟),否则空闲连接容易变成“僵尸事务”长期持锁
最危险的盲区是:以为 INNODB_TRX 里没事务就安全了。MDL锁不依赖显式事务,自动提交的语句在执行期间也持锁——只是时间短,不易观察;真正致命的是那些忘了 COMMIT 的连接。











