一 简介
上一篇文章 《MetaData Lock 之一》 简单的介绍了MySQL 引入MDL 的前因后果,本文深入了解MDL的实现原理和运行机制。
二 MDL 的类型
metadata lock也是一种锁。每个metadata lock都会定义锁住的对象,锁的持有时间和锁的类型。
2.1 按照对象/范围划分
|
属性
|
含义
|
范围/对象
|
|
GLOBAL
|
全局锁
|
范围
|
|
COMMIT
|
提交保护锁
|
范围
|
|
SCHEMA
|
库锁
|
对象
|
|
TABLE
|
表锁
|
对象
|
|
FUNCTION
|
函数锁
|
对象
|
|
PROCEDURE
|
存储过程锁
|
对象
|
|
TRIGGER
|
触发器锁
|
对象
|
|
EVENT
|
事件锁
|
对象
|
MDL按锁住的对象来分类,可以分为global,commit,schema, table, function,procedure,trigger,event,这些对象发生锁等待时,我们在show processlist可以分别看到如下等待信息。
-
Waiting for global read lock
-
Waiting for commit lock
-
Waiting for schema metadata lock
-
Waiting for table metadata lock
-
Waiting for stored function metadata lock
-
Waiting for stored procedure metadata lock
-
Waiting for trigger metadata lock
-
Waiting for event metadata lock
2.2 按照锁的持有时间
|
属性
|
含义
|
|
MDL_STATEMENT
|
从语句开始执行时获取,到语句执行结束时释放。
|
|
MDL_TRANSACTION
|
在一个事务中涉及所有表获取MDL,一直到事务commit或者rollback(线程中终清理)才释放。
|
|
MDL_EXPLICIT
|
需要MDL_context::release_lock()显式释放。
语句或者事务结束,也仍然持有,如
Lock table, flush .. with lock语句等。
|
2.3 按照操作的对象
|
属性
|
含义
|
事例
|
|
MDL_INTENTION_EXCLUSIVE(IX)
|
意向排他锁用于global和commit的加锁。
|
truncate table t1;
insert into t1
values(3,'abcde');会加如下锁(GLOBAL,MDL_STATEMENT,MDL_INTENTION_EXCLUSIVE)
(SCHEMA,MDL_TRANSACTION,MDL_INTENTION_EXCLUSIVE)
|
|
MDL_SHARED(S)
|
只访问元数据 比如表结构,不访问数据。
|
set golbal_read_only =on
加锁
(GLOBAL,MDL_EXPLICIT,MDL_SHARED)
|
|
MDL_SHARED_HIGH_PRIO(SH)
|
用于访问information_scheam表,不涉及数据。
|
select * from
information_schema.tables;
show create table xx; desc xxx;会加如下锁:
(TABLE,MDL_TRANSACTION,MDL_SHARED_HIGH_PRIO)
|
|
MDL_SHARED_READ(SR)
|
访问表结构并且读表数据
|
select * from t1; lock table t1
read;
会加如下锁:
(TABLE,MDL_TRANSACTION,MDL_SHARE_READ)
|
|
MDL_SHARED_WRITE(SW)
|
访问表结构并且写表数据
|
insert/update/delete/select ..
for update
会加如下锁:
(TABLE,MDL_TRANSACTION,MDL_SHARE_WRITE)
|
|
MDL_SHARED_UPGRADABLE(SU)
|
是mysql5.6引入的新的metadata
lock,
在alter table/create index/drop index会加该锁;可以说是为了online
ddl才引入的。特点是允许DML,防止DDL;
|
(TABLE,MDL_TRANSACTION,MDL_SHARED_UPGRADABLE)
|
|
MDL_SHARED_NO_WRITE(SNW)
|
可升级锁,访问表结构并且读写表数据,并且禁止其它事务写。
|
alter table t1 modify c
bigint;(非onlineddl)
(TABLE,MDL_TRANSACTION,MDL_SHARED_NO_WRITE)
|
|
MDL_SHARED_NO_READ_WRITE(SNRW)
|
可升级锁,访问表结构并且读写表数据,并且禁止其它事务读写。
|
lock table t1 write;加锁
(TABLE,MDL_TRANSACTION,MDL_SHARED_NO_READ_WRITE
|
|
MDL_EXCLUSIVE(X)
|
防止其他线程读写元数据
|
CREATE/DROP/RENAME
TABLE,其他online DDL在rename阶段也持有X锁
(TABLE,MDL_TRANSACTION,MDL_EXCLUSIVE)
|
关于global对象
主要作用是防止DDL和写操作的过程中,执行set golbal_read_only =on或flush tables with read lock;
关于commit对象锁
主要作用是执行flush tables with read lock后,防止已经开始在执行的写事务提交。
insert/update/delete在提交时都会上(COMMIT,MDL_EXPLICIT,MDL_INTENTION_EXCLUSIVE)锁。
2.4 MDL 锁的兼容性矩阵
三、几种典型语句的加(释放)锁流程
1.select语句操作MDL锁流程
1)Opening tables阶段,加共享锁
a) 加MDL_INTENTION_EXCLUSIVE锁
b) 加MDL_SHARED_READ锁
2)事务提交阶段,释放MDL锁
a) 释放MDL_INTENTION_EXCLUSIVE锁
b) 释放MDL_SHARED_READ锁
2. DML语句操作MDL锁流程
1)Opening tables阶段,加共享锁
a) 加MDL_INTENTION_EXCLUSIVE锁
b) 加MDL_SHARED_WRITE锁
2)事务提交阶段,释放MDL锁
a) 释放MDL_INTENTION_EXCLUSIVE锁
b) 释放MDL_SHARED_WRITE锁
3. alter操作MDL锁流程
1)Opening tables阶段,加共享锁
a) 加MDL_INTENTION_EXCLUSIVE锁
b) 加MDL_SHARED_UPGRADABLE锁,升级到MDL_SHARED_NO_WRITE锁
2)操作数据,copy data,流程如下:
a) 创建临时表tmp,重定义tmp为修改后的表结构
b) 从原表读取数据插入到tmp表
3)将MDL_SHARED_NO_WRITE读锁升级到MDL_EXCLUSIVE锁
a) 删除原表,将tmp重命名为原表名
4)事务提交阶段,释放MDL锁
a) 释放MDL_INTENTION_EXCLUSIVE锁
b) 释放MDL_EXCLUSIVE锁
四、典型问题分析
通常情况下我们关注MDL锁,大部分情况都是线上DB出现异常了。那么出现异常后,我们如何去判断是MDL锁导致的呢。监视MDL锁主要有两种方法,一种是通过show processlist命令,判断是否有事务处于“Waiting for table metadata lock”状态,另外就是通过mysql的profile,分析特定语句在每个阶段的耗时时间。
抛出几个问题:
select 与alter是否会相互阻塞
dml与alter是否会相互阻塞
select与DML是否会相互阻塞
结合第三节几种语句的上锁流程,我们很容易得到这三个问题的答案。语句会在阻塞在具体某个环节,可以通过profile来验证我们的答案是否正确。
第一个问题,当执行select语句时,只要select语句在获取MDL_SHARED_READ锁之前,alter没有执行到rename阶段,那么select获取MDL_SHARED_READ锁成功,后续有alter执行到rename阶段,请求MDL_EXCLUSIVE锁时,就会被阻塞。rename阶段会持有MDL_EXCLUSIVE锁,但由于这个过程时间非常短(大头都在copy数据阶段),并且是alter的最后一个阶段,所以基本感觉不到alter会阻塞select语句。由于MDL锁在事务提交后才释放,若线上存在大查询,或者存在未提交的事务,则会出现ddl卡住的现象。这里要注意的是,ddl卡住后,若再有select查询或DML进来,都会被堵住,就会出现threadrunning飙高的情况。
第二个问题,alter在opening阶段会将锁升级到MDL_SHARED_NO_WRITE,rename阶段再将升级为MDL_EXCLUSIVE,由于MDL_SHARED_NO_WRITE与MDL_SHARED_WRITE互斥,所以先执行alter或先执行DML语句,都会导致语句阻塞在opening tables阶段。
第三个问题,显然,由于MDL_SHARED_WRITE与MDL_SHARED_READ兼容,所以它们不会因为MDL而导致等待的情况。
五 参考资料
[1] 《
MDL 锁介绍二 》他的三篇文章都值得一读,强烈推荐,本系列文章也是参考他的blog 写的。
[2] 《
DDL操作导致备库复制中断》 @印风的blog 为alisql/RDS 打了N多patch的牛人。
如果您觉得从这篇文章受益,可以微信支付赞助 北在南方 一瓶饮料 ^_^
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/22664653/viewspace-1791608/
,如需转载,请注明出处,否则将追究法律责任。