线上服务突然卡顿,接口超时暴涨,排查到MySQL时发现,大量会话状态显示Waiting for table metadata lock 。 明明只是执行一个简单的ALTER TABLE加字段,怎么就把整个表的查询都堵死了?
这一切的“始作俑者”,就是MySQL里存在感极强却又容易被忽略的MDL锁(元数据锁,Metadata Lock)。
很多人吐槽MDL锁“碍事”,却不知道:如果没有MDL锁,你的数据库早就乱成一锅粥了。MySQL为什么需要MDL锁呢?
一、 什么是MDL锁?
在聊“为什么需要”之前,先明确一个基础认知:MDL锁到底是什么?
MDL锁,全称元数据锁,是MySQL 5.5版本引入的一种锁机制,核心作用是保护表的元数据完整性。这里的“元数据”,简单说就是表的“说明书”——比如表有哪些字段、字段类型是什么、有没有索引、主键是什么,这些描述表结构的信息,都属于元数据。
很多人会把MDL锁和行锁、表锁搞混,这里用一句话区分清楚:
关键一点:MDL锁是MySQL自动管理的,不需要我们手动加锁、解锁,只要访问表(不管是查数据还是改结构),MySQL就会自动给表加上对应的MDL锁。
二、 MySQL为什么非要加MDL锁?
答案很简单:为了避免“表结构”和“表数据”的并发冲突,保证数据一致性。
我们可以把数据库想象成一个图书馆,表是书架,数据是书架上的书,元数据就是书架的“分类标签”(比如“计算机类”“文学类”)。MDL锁,就是保护这个“分类标签”不被乱改的规则。
如果没有这个规则,会发生什么?我们看3个真实场景。
场景1:没有MDL锁,查询会读到“不存在的字段”
假设两个会话同时操作一张表:
如果没有MDL锁,会话1的查询可能会出现两种诡异情况:要么查询到一半,突然发现name字段不存在,直接报错;要么读到残缺的数据,导致业务逻辑异常。
而有了MDL锁,情况就不一样了:会话1执行查询时,MySQL会自动加MDL读锁;会话2执行DDL时,需要申请MDL写锁——读锁和写锁互斥,会话2会被阻塞,直到会话1的查询结束、释放读锁,才能执行改表操作。这样就避免了“查询读到不存在字段”的问题。
场景2:没有MDL锁,事务隔离性会被破坏
MySQL的事务隔离级别(比如可重复读),要求同一事务内多次读取的数据保持一致。但如果没有MDL锁,表结构被中途修改,这个一致性就会被打破。
举个例子:会话1开启事务,第一次查询用户表,看到表中有id、name两个字段;此时会话2修改表结构,新增了age字段并提交;会话1在同一个事务中再次查询,突然多了一个age字段。这就违反了“可重复读”的隔离要求,会导致业务逻辑混乱。
MDL锁的存在,就能解决这个问题:会话1开启事务后,持有MDL读锁,在事务未提交前,会一直持有该锁,阻止会话2修改表结构。直到会话1提交事务、释放读锁,会话2才能执行DDL操作,保证了事务隔离性的实现。
场景3:没有MDL锁,会导致binlog日志错乱
MySQL的binlog用于记录数据库的所有变更,是数据恢复、主从同步的核心。如果没有MDL锁,DDL操作和DML操作的执行顺序可能被打乱,导致binlog日志错乱,进而引发主从数据不一致。
比如MySQL 5.5之前没有MDL锁时,曾出现过一个经典bug:会话1执行INSERT插入数据(未提交),会话2执行DROP TABLE删除表并提交,此时binlog会先记录DROP TABLE,再记录INSERT——恢复数据时,会先删除表,再执行插入,直接报错,导致数据丢失。
MDL锁引入后,这种问题就被彻底解决了:会话1执行DML时持有MDL读锁,会话2执行DDL(DROP TABLE)需要MDL写锁,会被阻塞,直到会话1提交事务、释放读锁,会话2才能执行,保证了binlog日志的顺序正确性。
三、 MDL锁的核心规则
理解了MDL锁的作用,再记住两个核心规则,就能避开80%的坑:
1. 锁的类型:DML操作(SELECT、INSERT、UPDATE、DELETE)会加MDL读锁;DDL操作(ALTER、DROP、CREATE INDEX)会加MDL写锁;
2. 兼容规则:读锁之间互不兼容?不!读锁之间可以共享(多个会话可以同时查一张表);读锁和写锁、写锁和写锁互斥(改表结构时,所有读写都要等)。
这里要特别注意一个高频坑:长事务会长期持有MDL读锁。比如一个事务开启后,执行了SELECT操作但一直不提交,就会一直持有MDL读锁,此时执行DDL操作会被阻塞,后续所有访问该表的DML操作也会被阻塞,最终导致数据库连接打满,服务雪崩。
四、 如何避免MDL锁阻塞?
结合线上实战经验,分享3个最实用的避坑技巧,建议收藏:
1. 避免长事务:尽量缩短事务执行时间,避免在事务中做无关操作(比如sleep、调用外部接口),执行完立即提交或回滚,防止长期持有MDL读锁;
2. 低峰期执行DDL:改表结构、加索引等DDL操作,尽量安排在业务低峰期(比如凌晨),减少对正常业务的影响;MySQL 5.6+支持Online DDL,可以通过ALTER TABLE ... ALGORITHM=INPLACE, LOCK=NONE,减少锁阻塞时间;
3. 及时排查阻塞:遇到Waiting for table metadata lock时,通过SELECT * FROM performance_schema.metadata_locks查看当前MDL锁持有情况,找到未提交的长事务,终止后即可释放锁。
五、总结
很多人吐槽MDL锁“碍事”,但其实它是MySQL的“守护者”。没有MDL锁,表结构和表数据的并发冲突会导致数据错乱、事务异常、主从同步失败,后果远比“阻塞”更严重。
MDL锁的核心价值,就是在并发环境中,守住表结构的一致性,协调DML和DDL的执行顺序。理解它的作用,记住它的规则,避开长事务的坑,就能让MDL锁从“麻烦”变成“保障”。
最后问一句:你遇到过MDL锁阻塞的问题吗?评论区说说你的排查经历,一起避坑~
关注我,后续分享更多MySQL实战技巧,避开所有数据库踩坑点!