为什么执行 alter 更新表要慎重?
说到 MySQL 中的锁,相信小伙伴们多多少少都能说出来一些,例如全局锁、表锁、行锁等等。
今天我想和大伙分享一个经常容易被忽略的锁,那就是 MDL,这个锁如果忽略了,可能会导致大问题,一起来看下。
1. MDL 锁介绍
MDL 全称为 meta data lock,中文称作元数据锁,这是从 MySQL5.5 开始引入的锁,MDL 是为了解决 DDL 操作和 DML 操作之间的一致性。从锁的作用范围上来说,MDL 算是一种表级锁,是一个 server 层的锁。
大家想想这样一个场景:当我们通过 alter 语句更新一张表的时候,同时又针对这张表执行了查询语句,假设两者同时执行,那么将来查询到的结果可能就并不是我们想要的结果,也就是数据一致性出了问题。
为了解决这个问题,从 MySQL5.5 开始推出了 MDL。
可能有小伙伴会说好像我从来没写过跟 MDL 有关的锁呀?
其实 MDL 加锁过程是系统自动控制,无法直接干预,也不需要直接干预,当我们对一个表做增删改查操作的时候,会自动加 MDL 读锁;当我们要更新表结构的时候,加 MDL 写锁。加读锁则所有线程可正常读表的元数据,并且读锁不影响表的增删改查操作,只是不能修改表结构;而加写锁则只有拥有锁的线程可以读写元数据,即只有拥有锁的线程才能更新表结构,其它线程不能修改表结构也不能执行相应的增删改查。
2. MDL 锁演示
接下来松哥通过一个简单的例子,来和大家演示 MDL 锁。
首先我们开启一个会话,开启一个事务,执行一个更新 SQL:
大家注意,由于事务没有提交,所以现在这个更新 SQL 还持有一个 MDL 读锁。
接下来我们开启一个新的会话,执行一个 alter 语句:
大家看到,在新的会话中执行 alter,就会卡住。卡住的原因在于 DDL 操作需要获取 MDL 写锁,而在 MDL 中,读读共享,读写互斥,写写互斥。
所以现在获取 MDL 写锁就会被卡住,进而导致 alter 语句被阻塞,除非前面的 update 事务提交了,释放了 MDL 读锁,那么 alter 语句就可以顺利拿到写锁,进而完成表的更新。
这就是 MDL 锁,不需要我们手动添加手动释放,系统会自动添加自动释放。
3. Online DDL
MySQL5.6 开始引入了 Online DDL,很多小伙伴可能对 Online DDL 有一些了解但是不够全面,松哥这里也和大家说一下,加深大家对 MDL 的理解。
Online DDL 从名字上就可以看出来是可以在线执行 DDL,不和其他操作冲突,具体执行流程如下:
DDL 操作需要首先获取 MDL 写锁。 接下来将 MDL 写锁降级成 MDL 读锁。 做真正的 DDL 操作,这一步也是最耗时的,由于此时我们持有的是 MDL 读锁,因此并不会阻塞其他的增删改查操作。 做完 DDL 操作之后,接下来将 MDL 读锁升级成 MDL 写锁。 释放 MDL 锁。
看了上面的分析大家就明白了,Online DDL 并不是让你随时随地可以更新表结构,也还是有限制的,只有在第三步那里可以继续执行增删改查,当然即使在第三步,更新表结构也是不允许的。在我们第二小节的案例中,第一步获取 MDL 写锁就被卡住了。
4. 一个隐藏的问题
和 MDL 锁相关的还有一个隐藏问题,如果不了解这个知识点,将来可能也会犯一些错误。
有小伙伴看了松哥上面的例子,可能会自己做下面这个实验:
首先在一个会话中开启事务,然后执行 alter,如下:
注意这个事务没有提交。
接下来执行开启另外一个会话,执行一条更新 SQL:
发现更新 SQL 竟然成功执行了,没有发生阻塞!!!这是怎么回事呢?
这里其实就涉及到 MySQL 事务的隐式提交:所有的 DDL 语句都会导致事务隐式提交,换句话说,当你在执行 DDL 语句前,事务就已经提交了。
这就意味着带有 DDL 语句的事务将来没有办法 rollback。
假设一个有一个 SQL 事务,类似下面这样:
begin;
A-SQL
alter
B-SQL
对于上面这个 SQL 而言,执行 alter 的时候,就会提交 A-SQL
所在的事务,执行 B-SQL
时,已经不在事务里边了。
这就要求我们在设计 DML 数据操作的事务时,尽量不要在 DML 中包含 DDL 语句。
好啦,和小伙伴们随便聊一聊 MDL 锁的问题,感兴趣的小伙伴可以自己动手尝试下哦。
参考资料:
http://dev.mysql.com/doc/refman/5.7/en/cannot-roll-back.html