看mysql45讲遇到一个问题:
为什么C等待拿锁之后,D也会阻塞?其实这里并没有解释清楚。因为如果按并发理解的话,C,D应当是同等级,都有可能拿到锁的。但C读写锁互斥,D读读不互斥,这样的话就跟上图所述相悖了。
首先是MDL(metaData Lock)的概念。元数据锁是server层的锁,表级锁,主要用于隔离DML(Data Manipulation Language,数据操纵语言,如select)和DDL(Data Definition Language,数据定义语言,如改表头新增一列)操作之间的干扰。每执行一条DML、DDL语句时都会申请MDL锁,DML操作需要MDL读锁,DDL操作需要MDL写锁(MDL加锁过程是系统自动控制,无法直接干预,读读共享,读写互斥,写写互斥)
申请MDL锁的操作会形成一个队列,队列中写锁获取优先级高于读锁。一旦出现写锁等待,不但当前操作会被阻塞,同时还会阻塞后续该表的所有操作。事务一旦申请到MDL锁后,直到事务执行完才会将锁释放。(这里有种特殊情况如果事务中包含DDL操作,mysql会在DDL操作语句执行前,隐式提交commit,以保证该DDL语句操作作为一个单独的事务存在,同时也保证元数据排他锁的释放,例如id 44的语句改为<begin;alter table testok add z varchar(10) not Null;select * from testok;>,此时一旦alter语句执行完成会马上提交事务(autocommit=1),后面的select就在本次事务之外,其执行完成后不会持有读锁)
这样就能解释通为什么session C被阻塞后,session D也运行不了的原因了。
为什么加了MDL写锁要阻塞后面的MDL锁?
假设一个MDL操作处于两个DML操作中间,而这个MDL操作删除了一列,如果后面的这个MDL操作刚好用到这一列,那不就出问题了吗?
为什么MDL锁会形成一个队列?
先拿到MDL锁的操作优先获得锁,拿到MDL写锁的操作会阻塞后面MDL锁的获取。
然后进入下一个问题:
我也试了一下,确实如此,加上begin之后,session D会“插队”到session C前面,也就是原本的队列结构竟然出现了 “插队”现象,???
这个问题就要涉及到online ddl了。
由于ddl读写互斥,严重影响性能,于是mysql推出了全新的online ddl概念,即通过:
- 拿MDL写锁
- 降级成MDL读锁
- 真正做DDL
- 升级成MDL写锁
- 释放MDL锁
5个步骤,第一步拿读锁是为确保没有其他ddl语句在执行;第三步是自己申请一块空间开始改表结构、填数据;等填好了之后,执行第四步,这期间由于持有读锁,可以确保不会有其他ddl语句造成不一致性;最后等拿到写锁,把表一替代就搞定了。
这样就看出来端倪了。上图中session A,Bcommit后,sessionC确实拿到了写锁,只不过由于锁降级,令sessionD拿到了读锁。但session D没有commit,因此session C在执行online commit到第三步后,又给阻塞了。所以就出现了类似于“插队”的现象。
如果想验证的话,可以再开一个begin;select...的session,就叫sessionE吧。然后让刚刚没commit的session D commit一下,会发现这次session C并没有阻塞。