元数据锁 (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
维护状态: ✅ 完整