Docker MySQL 删表卡死、执行超时(MDL锁阻塞)问题复盘与根治方案
一、问题现象
用户在 Docker 部署的 MySQL 环境中执行删表、清空数据操作,出现以下现象:
-
DROP TABLE 一直执行、不报错、不结束
-
普通 SELECT 查询同一张表也被阻塞排队
-
无死锁报错、无超时报错、线程一直挂起
-
Docker 资源(CPU/内存/磁盘)无明显瓶颈,排除容器资源不足问题
阻塞目标表:ladder_dw.rel_rectification_8d
二、现场排查结论(核心根因)
1. 根本原因:长耗时 ETL 大事务持有 MDL 元数据共享锁
业务存在一条超大 CTE 嵌套 INSERT ... SELECT 同步 SQL:
-
单条语句包含多层 CTE、窗口函数、多表 JOIN、字符集强制转换
-
执行时长高达 5000+ 秒,属于超大单事务
-
SQL 中关联读取了
rel_rectification_8d表 -
MySQL 特性:单条 INSERT...SELECT 属于一个完整事务,执行期间不提交
因此:ETL 运行期间持续持有该表 MDL 共享读锁
2. MDL 锁阻塞机制(本次阻塞核心逻辑)
-
共享 MDL(读锁):SELECT、INSERT、查询、ETL 均可持有,互不阻塞
-
排他 MDL(写锁):DROP / ALTER / TRUNCATE 需要独占
一旦有 ETL 持有共享锁:
DROP 操作 → 等待排他锁 → 永久阻塞
并且触发锁队列雪崩:后续所有 SELECT、DML 全部排队卡住。
3. 叠加坑点:DBeaver 客户端隐性长事务
DBeaver 默认 autocommit=OFF:
-
执行 SELECT 不手动 COMMIT,连接变为 Sleep 状态
-
只读事务不写入数据,
innodb_trx查询为空(极难排查) -
但依旧持有 MDL 共享锁,持续阻塞 DDL
三、现场完整排查过程
1. 查看阻塞线程
Plain
show full processlist;
现场发现三类关键线程:
-
112 线程:超大 ETL INSERT 长期 executing(根因)
-
120 线程 :DROP TABLE 卡在
Waiting for table metadata lock -
134 线程:普通 SELECT 被连锁阻塞
2. 排查未提交事务
Plain
SELECT trx_id, trx_started, trx_query, trx_mysql_thread_id
FROM information_schema.innodb_trx;
kill ETL 后结果为空,说明 无残留 InnoDB 事务。
3. 排查 MDL 元数据锁(最终确认无锁)
Plain
SELECT OBJECT_TYPE,OBJECT_SCHEMA,OBJECT_NAME,LOCK_TYPE,LOCK_DURATION,LOCK_STATUS,OWNER_THREAD_ID
FROM performance_schema.metadata_locks
WHERE OBJECT_SCHEMA='ladder_dw' AND OBJECT_NAME='rel_rectification_8d';
查询结果为空 → 锁完全释放,可安全删表。
四、紧急处理操作(现场已执行)
1. 终止阻塞源
kill 长期运行的 ETL 大事务线程,触发事务回滚、释放 MDL 锁。
Plain
kill [线程ID];
2. 释放客户端隐性锁
关闭所有 DBeaver 闲置会话、重连数据库,清除 Sleep 长事务 MDL 锁。
3. 执行删表
锁清空后正常执行删表(MySQL 不支持 CASCADE,去除关键字):
Plain
DROP TABLE ladder_dw.rel_rectification_8d;
五、本次故障的 SQL 底层性能缺陷(根治重点)
本次阻塞不仅仅是锁问题,ETL SQL 本身存在严重性能问题,导致执行过久、长期占锁:
1. 大量字段强制 COLLATE,索引完全失效
SQL 中大量出现:COLLATE utf8mb4_unicode_ci
-
JOIN 条件、字段查询强制字符集转换
-
导致所有关联字段索引失效、全表扫描
-
执行时间成倍拉长,锁持有时间爆炸增长
2. 多层 CTE + 全局窗口函数
ROW_NUMBER() OVER(PARTITION BY ... ORDER BY ID DESC) 全局排序:
-
大表全量内存排序,CPU/内存消耗极高
-
无法增量执行,必须一次性跑完所有数据
3. 超大单事务写入
整段几万行数据、多表关联计算合并为单条 INSERT 事务:
-
执行数小时不提交
-
长期持有多张业务表 MDL 锁
-
kill 后需要超大 undo 回滚,IO 压力极高
六、永久根治优化方案(杜绝以后再次卡死)
1. ETL 架构改造(核心方案)
禁止单条巨型 CTE INSERT 直接写业务表,改为两步走:
-
第一步:复杂计算落地临时表(临时表无 MDL 锁干扰业务表)
-
第二步:分批增量写入正式表(limit 1000 分批提交)
优势:
-
复杂计算阶段不占用业务表 MDL 锁
-
小事务写入,锁持有极短
-
出错可断点续跑、回滚代价极小
2. 数据库字段标准化
统一所有关联字段排序规则:utf8mb4_unicode_ci
彻底删除 SQL 中所有 COLLATE 强制转换,恢复索引命中。
3. DBeaver 客户端规范(必须配置)
连接属性开启:Auto-commit 自动提交
杜绝:执行 SELECT 不提交 → Sleep 长事务 → 隐性 MDL 锁阻塞。
4. 大表 DDL 运维规范
生产/数仓环境删大表禁止直接 DROP:
Plain
rename table 表名 to 表名_bak;
drop table 表名_bak;
rename 秒级完成、MDL 锁极短,无阻塞风险。
七、后续快速排查脚本(存档备用)
1. 查看未提交事务
Plain
SELECT trx_id, trx_started, trx_query, trx_mysql_thread_id
FROM information_schema.innodb_trx;
2. 精准查询表 MDL 锁
Plain
SELECT OBJECT_TYPE,OBJECT_SCHEMA,OBJECT_NAME,LOCK_TYPE,LOCK_DURATION,LOCK_STATUS,OWNER_THREAD_ID
FROM performance_schema.metadata_locks
WHERE OBJECT_SCHEMA='ladder_dw' AND OBJECT_NAME='rel_rectification_8d';
3. 查看表外键依赖(删表报错专用)
Plain
SELECT TABLE_NAME, CONSTRAINT_NAME
FROM information_schema.KEY_COLUMN_USAGE
WHERE REFERENCED_TABLE_SCHEMA = 'ladder_dw'
AND REFERENCED_TABLE_NAME = 'rel_rectification_8d';
八、问题总结
本次 Docker MySQL 删表超时、卡死 与 Docker 容器本身无关,属于典型的:
不良 ETL SQL 设计 + MySQL MDL 锁机制 + 客户端隐性长事务 叠加导致的阻塞事故。
优化后可彻底解决:删表卡顿、DDL 阻塞、查询排队、长事务锁表等一系列问题。
(注:部分内容可能由 AI 生成)