4. 元数据锁(Metadata Lock)基础

元数据锁(Metadata Lock,简称 MDL)是 MySQL 内部用于保护数据库对象(表、视图、存储过程等)元数据一致性的锁机制。在 DDL 与 DML 并发执行时,MDL 是保证元数据不出现逻辑损坏的核心防线。理解 MDL 的类型与行为,是排查 Liquibase 迁移过程中因锁等待导致 GC 停顿或连接池耗尽的前提。

4.1 什么是 MDL

MDL 不是表锁或行锁,它锁的是对象的定义。当会话 A 正在执行一条 SELECT 时,MySQL 会在该表上获取一个 MDL 共享锁,防止其他会话在此期间修改表结构。如果会话 B 尝试执行 ALTER TABLE,它需要获取 MDL 排他锁,此时必须等待会话 A 释放共享锁。

关键认知: MDL 等待不会直接导致 GC 停顿,但长时间未释放的 MDL 会阻塞后续所有 DDL/DML,导致连接堆积、事务超时,最终触发应用线程阻塞和 JVM 堆内存压力上升,间接引发 GC 停顿。

4.2 MDL 的类型

MySQL 8.0 将 MDL 分为以下主要类型,按锁强度从低到高排列:

锁类型 缩写 强度 典型场景
MDL_SHARED S 低 SELECT、INSERT、UPDATE、DELETE
MDL_SHARED_READ SR 低 SELECT … FOR SHARE
MDL_SHARED_WRITE SW 中 DML(INSERT/UPDATE/DELETE)
MDL_SHARED_UPGRADABLE SU 中高 ALTER TABLE … ALGORITHM=INPLACE(准备阶段)
MDL_EXCLUSIVE X 高 ALTER TABLE、DROP TABLE、CREATE INDEX
排错要点: 在 performance_schema.metadata_locks 表中,LOCK_STATUS 为 PENDING 且 LOCK_TYPE 为 EXCLUSIVE 的记录,通常就是阻塞链的源头。

4.3 MDL 在 DDL/DML 中的行为

MDL 的获取与释放遵循以下规则:

  • DML 获取共享锁(S/SW):事务开始时获取,事务提交或回滚后释放。长事务会长时间持有 MDL 共享锁。
  • DDL 获取排他锁(X):DDL 语句执行前需要等待所有共享锁释放,执行期间阻塞所有后续 DML。
  • 锁升级路径:ALTER TABLE 通常先获取 SU 锁(允许并发 DML),在表重建或元数据提交阶段升级为 X 锁。
  • 等待队列:X 锁请求会排在等待队列头部,后续 S 锁请求即使早于 X 锁到达,也会被 X 锁阻塞(写优先)。
-- 查看当前 MDL 等待情况(MySQL 8.0)
SELECT 
  OBJECT_SCHEMA,
  OBJECT_NAME,
  LOCK_TYPE,
  LOCK_STATUS,
  OWNER_THREAD_ID,
  WAITER_THREAD_ID
FROM performance_schema.metadata_locks
WHERE LOCK_STATUS = 'PENDING';
Liquibase 迁移陷阱: 如果 Liquibase 的 changelog 中包含了多个 DDL 操作,且迁移事务未及时提交,或者应用侧存在长事务读取被迁移的表,就会形成 MDL 等待链。等待链导致连接池线程阻塞,进而引发 JVM 线程饥饿和 GC 压力。

4.4 MDL 等待链与 GC 停顿的关联

下图展示了 MDL 等待如何从数据库层传导至应用层,最终触发 GC 停顿:

MDL 等待链与 GC 停顿传导路径 MDL 等待链 → 连接池阻塞 → GC 停顿传导路径 长事务 DML 持有 MDL 共享锁 (S) Liquibase DDL 等待 MDL 排他锁 (X) 后续 DML 阻塞 排队等待 X 锁释放 持有 S 锁 等待 X 锁 连接池耗尽 线程全部阻塞 JVM 堆压力上升 请求堆积、对象滞留 GC 停顿 Full GC / STW 线程阻塞 排错关键点: 1. 在 performance_schema.metadata_locks 中定位 LOCK_STATUS='PENDING' 的 X 锁请求。 2. 通过 OWNER_THREAD_ID 找到持有 S 锁的长事务,强制终止或等待其提交。 3. 在 Liquibase 迁移前设置 lock_wait_timeout,避免无限期等待。

4.5 排查 MDL 的常用命令

命令/视图 用途 适用版本
SHOW PROCESSLIST 查看处于 Waiting for table metadata lock 状态的线程 5.6+
performance_schema.metadata_locks 查看所有 MDL 锁的持有与等待关系 8.0+
sys.schema_table_lock_waits 可视化 MDL 等待链(依赖 performance_schema) 8.0+
SELECT * FROM information_schema.INNODB_TRX 查找长时间未提交的事务 5.6+
最佳实践: 在 Liquibase 迁移脚本中,始终在 DDL 前执行 SET SESSION lock_wait_timeout = 5,避免迁移线程被 MDL 无限阻塞。同时,监控 performance_schema.metadata_locks 中 PENDING 状态的记录数,作为告警指标。