Skip to content

元数据锁 (Metadata Lock / MDL) ​

📋 概述 ​

元数据锁(Metadata Lock,简称 MDL)是数据库用于保护表结构元数据的一种锁机制。它确保在执行 DDL(数据定义语言)操作时,不会有其他事务正在修改表数据,反之亦然。

核心特点 ​

✅ 优点:

  • 保护元数据: 防止表结构在查询时被修改
  • 自动管理: 数据库自动加锁和释放
  • 保证一致性: 确保 DDL 和 DML 不冲突
  • 透明性: 用户无需显式指定

❌ 缺点:

  • 可能阻塞: 长事务会阻塞 DDL 操作
  • 难以排查: 锁信息不易查看
  • 意外等待: DDL 可能长时间等待
  • 版本差异: 不同数据库实现不同

适用场景 ​

  • DDL 操作: ALTER TABLE, DROP TABLE, CREATE INDEX
  • 表结构修改: 添加/删除列、修改列类型
  • 索引维护: 创建或删除索引
  • 所有涉及元数据的操作

🔧 工作原理 ​

锁的类型 ​

1. MDL 共享锁 (MDL_SHARED) ​

DML 操作自动获取:

sql
SELECT * FROM users;  -- 自动获取 MDL 共享锁
UPDATE users SET name = 'test';  -- 自动获取 MDL 共享锁
INSERT INTO users VALUES (...);  -- 自动获取 MDL 共享锁
DELETE FROM users WHERE id = 1;  -- 自动获取 MDL 共享锁

2. MDL 排他锁 (MDL_EXCLUSIVE) ​

DDL 操作自动获取:

sql
ALTER TABLE users ADD COLUMN age INT;  -- 需要 MDL 排他锁
DROP TABLE users;  -- 需要 MDL 排他锁
CREATE INDEX idx_name ON users(name);  -- 需要 MDL 排他锁

锁兼容性 ​

        | MDL_SHARED | MDL_EXCLUSIVE
--------|------------|---------------
SHARED  |     ✅     |      ❌
EXCLUSIVE |   ❌     |      ❌

示例:

事务A: SELECT * FROM users;  -- 持有 MDL_SHARED
事务B: ALTER TABLE users ...;  -- 需要 MDL_EXCLUSIVE,被阻塞

💻 各数据库实现 ​

MySQL (5.5+) ​

MySQL 5.5 引入了 MDL 机制。

基本行为 ​

sql
-- 事务 A
BEGIN;
SELECT * FROM users;  -- 持有 MDL_SHARED
-- 执行其他操作(60秒)
COMMIT;

-- 事务 B(同时)
ALTER TABLE users ADD COLUMN age INT;
-- ❌ 被阻塞!等待事务 A 释放 MDL_SHARED

查看 MDL 锁 ​

sql
-- MySQL 8.0+
SELECT 
    OBJECT_TYPE,
    OBJECT_SCHEMA,
    OBJECT_NAME,
    LOCK_TYPE,
    LOCK_STATUS,
    OWNER_THREAD_ID
FROM performance_schema.metadata_locks;

-- 查看 MDL 等待
SELECT * FROM performance_schema.metadata_locks 
WHERE LOCK_STATUS = 'PENDING';

常见问题 ​

长事务阻塞 DDL:

sql
-- 查找长时间持有 MDL 的事务
SELECT 
    t.trx_id,
    t.trx_started,
    t.trx_state,
    p.info
FROM information_schema.innodb_trx t
JOIN information_schema.processlist p ON t.trx_mysql_thread_id = p.id
WHERE t.trx_state = 'RUNNING'
  AND TIMESTAMPDIFF(SECOND, t.trx_started, NOW()) > 10;

-- 终止长事务
KILL <process_id>;

PostgreSQL ​

PostgreSQL 使用 AccessShareLock 和 AccessExclusiveLock:

sql
-- 查看元数据锁
SELECT 
    l.relation::regclass AS table_name,
    l.mode,
    a.pid,
    a.query
FROM pg_locks l
JOIN pg_stat_activity a ON l.pid = a.pid
WHERE l.mode IN ('AccessShareLock', 'AccessExclusiveLock');

🎯 最佳实践 ​

✅ 推荐做法 ​

1. 缩短事务时间 ​

sql
-- ✅ 推荐:快速提交
BEGIN;
SELECT * FROM users WHERE id = 1;
COMMIT;

-- 然后执行 DDL
ALTER TABLE users ADD COLUMN age INT;

2. 业务低峰期执行 DDL ​

bash
# 在凌晨执行 DDL
0 2 * * * mysql -e "ALTER TABLE users ADD COLUMN age INT;"

3. 使用在线 DDL ​

sql
-- MySQL 8.0+ 在线添加索引
ALTER TABLE users ADD INDEX idx_name (name), ALGORITHM=INPLACE, LOCK=NONE;

❌ 避免陷阱 ​

1. 避免长事务阻塞 DDL ​

sql
-- ❌ 危险:长时间持有 MDL
BEGIN;
SELECT * FROM users;
SLEEP(300);  -- 5分钟
COMMIT;

-- 这期间所有 DDL 都被阻塞

🚨 常见问题 ​

Q1: 如何排查 MDL 阻塞? ​

答:

sql
-- 查找阻塞源
SELECT * FROM performance_schema.metadata_locks 
WHERE LOCK_STATUS = 'PENDING';

-- 终止持有锁的事务
KILL <process_id>;

Q2: MDL 会自动释放吗? ​

答: 是的,事务结束时自动释放。


📚 相关资源 ​

内部链接 ​

外部资源 ​


最后更新: 2026-04-12
维护状态: ✅ 完整

Released under MIT License.