一、MDL锁的作用

MySQL DBA 对于 Waiting for table metadata lock 肯定不会陌生,一般都是进行 alter 操作时被堵住了,导致了我们在 show processlist 时,看到线程的状态是在等 metadata lock。

为了在并发环境下维护表元数据的数据一致性,在表上有活动事务(显式或隐式)的时候,不可以对元数据进行写入操作。因此从MySQL5.5版本开始引入了MDL锁(metadata lock),来保护表的元数据信息,用于解决或者保证DDL操作与DML操作之间的一致性。

对于引入MDL,其主要解决了2个问题,一个是事务隔离问题,比如在可重复隔离级别下,会话A在2次查询期间,会话B对表结构做了修改,两次查询结果就会不一致,无法满足可重复读的要求;另外一个是数据复制的问题,比如会话A执行了多条更新语句期间,另外一个会话B做了表结构变更并且先提交,就会导致slave在重做时,先重做alter,再重做update时就会出现复制错误的现象。

所以在对表进行上述操作时,如果表上有活动事务(未提交或回滚),请求写入的会话会等待在Metadata lock wait 。例如下面的这种情形:

若没有MDL锁的保护,则事务2可以直接执行DDL操作,并且导致事务1出错,5.1版本即是如此。5.5版本加入MDL锁就在于保护这种情况的发生,由于事务1开启了查询,那么获得了MDL锁,锁的模式为SHARED_READ,事务2要执行DDL,则需获得EXCLUSIVE锁,两者互斥,所以事务2需要等待。

注:支持事务的InnoDB引擎表和不支持事务的MyISAM引擎表,都会出现Metadata Lock Wait等待现象。一旦出现Metadata Lock Wait等待现象,后续所有对该表的访问都会阻塞在该等待上,导致连接堆积,业务受影响。

二、MDL锁类型说明

InnoDB层已经有了IS、IX这样的意向锁,有同学觉得可以用来实现上述例子的并发控制。但由于MySQL是Server-Engine架构,所以MDL锁是在Server中实现。另外,MDL锁还能实现其他粒度级别的锁,比如全局锁、库级别的锁、表空间级别的锁,这是InnoDB存储引擎层不能直接实现的锁。

但与 InnoDB 锁的实现一样,MDL锁也是类似对一颗树的各个对象从上至下进行加锁(对树进行加锁具体见:《MySQL技术内幕:InnoDB存储引擎》)。但是MDL锁对象的层次更多,简单来看有如下的层次:

上图中显示了最常见的 4 种 MDL 锁的对象,并且注明了常见的 SQL 语句会触发的锁。与 InnoDB 层类似的是,某些类型的 MDL 锁会从上往下一层层进行加锁。比如 LOCK TABLE … WRITE 这样的 SQL 语句,其首先会对 GLOBAL 级别加 INTENTION_EXCLUSIVE 锁,再对 SCHEMA 级别加 INTENTION_EXCLUSIVE 锁,最后对 TABLE 级别加 SHARED_NO_READ_WRITE 锁。

Metadata Lock 既然也是一种锁,所以每个 MDL 都会定义锁住的对象,锁的持有时间和锁的类型。

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

1

2

3

4

5

6

7

8

Waitingforglobalreadlock

Waitingforcommitlock

Waitingforschemametadatalock

Waitingfortablemetadatalock

Waitingforstoredfunctionmetadatalock

Waitingforstoredproceduremetadatalock

Waitingfortriggermetadatalock

Waitingforeventmetadatalock

2. 按照锁的持有时间

属性

含义

MDL_STATEMENT

从语句开始执行时获取,到语句执行结束时释放。

MDL_TRANSACTION

在一个事务中涉及所有表获取MDL,一直到事务commit或者rollback(线程中终清理)才释放。

MDL_EXPLICIT

需要MDL_context::release_lock()显式释放。语句或者事务结束,也仍然持有,如Lock table, flush .. with lock语句等。

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)

是MySQL 5.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)锁。

三、常见SQL加锁流程

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锁

4. FLUSH TABLE WITH READ LOCK语句操作MDL锁流程

1)加 MDL_EXPLICIT GLOBAL 锁(lock_global_read_lock)

2)清理表缓存(close_cached_tables)

3)加 MDL_EXPLICIT COMMIT 锁(make_global_read_lock_block_commit)

4)unlock 后释放 GLOBAL 锁和 COMMIT 锁

四、常见MDL锁场景

a)当前有执行DML操作时执行ALTRE操作。

# SESSION A

mysql> insert into sbtest2 select * from sbtest1;

# SESSION B

mysql> alter table sbtest2 add test1 int; //等待SESSION A执行完;

# SESSION C

mysql> show processlist;

+-----+------+-----------+--------+---------+------+---------------------------------+-------------------------------------------+

| Id | User | Host | db | Command | Time | State | Info |

+-----+------+-----------+--------+---------+------+---------------------------------+-------------------------------------------+

| 267 | root | localhost | sbtest | Query | 7 | Sending data | insert into sbtest2 select * from sbtest1 |

| 271 | root | localhost | sbtest | Query | 3 | Waiting for table metadata lock | alter table sbtest2 add test1 int |

| 272 | root | localhost | NULL | Query | 0 | starting | show processlist |

+-----+------+-----------+--------+---------+------+---------------------------------+-------------------------------------------+

3 rows in set (0.00 sec)

# SESSION D

mysql> select * from sbtest2 limit 10; //等待元数据锁;

# SESSION E

mysql> show processlist;

+-----+------+-----------+--------+---------+------+---------------------------------+-------------------------------------------+

| Id | User | Host | db | Command | Time | State | Info |

+-----+------+-----------+--------+---------+------+---------------------------------+-------------------------------------------+

| 267 | root | localhost | sbtest | Query | 20 | Sending data | insert into sbtest2 select * from sbtest1 |

| 271 | root | localhost | sbtest | Query | 13 | Waiting for table metadata lock | alter table sbtest2 add test1 int |

| 272 | root | localhost | NULL | Query | 0 | starting | show processlist |

| 308 | root | localhost | sbtest | Query | 3 | Waiting for table metadata lock | select * from sbtest2 limit 10 |

+-----+------+-----------+--------+---------+------+---------------------------------+-------------------------------------------+

4 rows in set (0.00 sec)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

# SESSION A

mysql>insertintosbtest2select*fromsbtest1;

# SESSION B

mysql>altertablesbtest2addtest1int;//等待SESSIONA执行完;

# SESSION C

mysql>showprocesslist;

+-----+------+-----------+--------+---------+------+---------------------------------+-------------------------------------------+

|Id|User|Host|db|Command|Time|State|Info|

+-----+------+-----------+--------+---------+------+---------------------------------+-------------------------------------------+

|267|root|localhost|sbtest|Query|7|Sendingdata|insertintosbtest2select*fromsbtest1|

|271|root|localhost|sbtest|Query|3|Waitingfortablemetadatalock|altertablesbtest2addtest1int|

|272|root|localhost|NULL|Query|0|starting|showprocesslist|

+-----+------+-----------+--------+---------+------+---------------------------------+-------------------------------------------+

3rowsinset(0.00sec)

# SESSION D

mysql>select*fromsbtest2limit10;//等待元数据锁;

# SESSION E

mysql>showprocesslist;

+-----+------+-----------+--------+---------+------+---------------------------------+-------------------------------------------+

|Id|User|Host|db|Command|Time|State|Info|

+-----+------+-----------+--------+---------+------+---------------------------------+-------------------------------------------+

|267|root|localhost|sbtest|Query|20|Sendingdata|insertintosbtest2select*fromsbtest1|

|271|root|localhost|sbtest|Query|13|Waitingfortablemetadatalock|altertablesbtest2addtest1int|

|272|root|localhost|NULL|Query|0|starting|showprocesslist|

|308|root|localhost|sbtest|Query|3|Waitingfortablemetadatalock|select*fromsbtest2limit10|

+-----+------+-----------+--------+---------+------+---------------------------------+-------------------------------------------+

4rowsinset(0.00sec)

若没有MDL锁的保护,则事务2可以直接执行DDL操作,并且导致事务1出错,5.1版本即是如此。5.5版本加入MDL锁就在于保护这种情况的发生,由于事务1开启了查询,那么获得了MDL锁,锁的模式为SHARED_READ,事务2要执行DDL,则需获得EXCLUSIVE锁,两者互斥,所以事务2需要等待。

注:支持事务的InnoDB引擎表和不支持事务的MyISAM引擎表,都会出现Metadata Lock Wait等待现象。一旦出现Metadata Lock Wait等待现象,后续所有对该表的访问都会阻塞在该等待上,导致连接堆积,业务受影响。

b)当前有对表的长时间查询或使用mysqldump/mysqlpump时,使用alter会被堵住。

# SESSION A

mysql> select *,sleep(10) from sbtest2;

# SESSION B

mysql> alter table sbtest2 add test2 int; //等待SESSION A执行完;

# SESSION C

mysql> show processlist;

+-----+------+-----------+--------+---------+------+---------------------------------+---------------------------------------+

| Id | User | Host | db | Command | Time | State | Info |

+-----+------+-----------+--------+---------+------+---------------------------------+---------------------------------------+

| 267 | root | localhost | sbtest | Query | 12 | User sleep | select *,sleep(10) from sbtest2 |

| 271 | root | localhost | sbtest | Query | 8 | Waiting for table metadata lock | alter table sbtest2 add test3 int |

| 272 | root | localhost | NULL | Query | 0 | starting | show processlist |

| 311 | root | localhost | NULL | Query | 3 | Waiting for table metadata lock | select * from sbtest.sbtest2 limit 10 |

+-----+------+-----------+--------+---------+------+---------------------------------+---------------------------------------+

4 rows in set (0.00 sec)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

# SESSION A

mysql>select*,sleep(10)fromsbtest2;

# SESSION B

mysql>altertablesbtest2addtest2int;//等待SESSIONA执行完;

# SESSION C

mysql>showprocesslist;

+-----+------+-----------+--------+---------+------+---------------------------------+---------------------------------------+

|Id|User|Host|db|Command|Time|State|Info|

+-----+------+-----------+--------+---------+------+---------------------------------+---------------------------------------+

|267|root|localhost|sbtest|Query|12|Usersleep|select*,sleep(10)fromsbtest2|

|271|root|localhost|sbtest|Query|8|Waitingfortablemetadatalock|altertablesbtest2addtest3int|

|272|root|localhost|NULL|Query|0|starting|showprocesslist|

|311|root|localhost|NULL|Query|3|Waitingfortablemetadatalock|select*fromsbtest.sbtest2limit10|

+-----+------+-----------+--------+---------+------+---------------------------------+---------------------------------------+

4rowsinset(0.00sec)

c)显示或者隐式开启事务后未提交或回滚,比如查询完成后未提交或者回滚,使用alter会被堵住。

# SESSION A

mysql> begin;

mysql> select * from sbtest2;

# SESSION B

mysql> alter table sbtest2 add test2 int; //等待SESSION A执行完;

# SESSION C

mysql> show processlist;

+-----+------+-----------+--------+---------+------+---------------------------------+-----------------------------------+

| Id | User | Host | db | Command | Time | State | Info |

+-----+------+-----------+--------+---------+------+---------------------------------+-----------------------------------+

| 267 | root | localhost | sbtest | Sleep | 36 | | NULL |

| 271 | root | localhost | sbtest | Query | 30 | Waiting for table metadata lock | alter table sbtest2 add test2 int |

| 272 | root | localhost | NULL | Query | 0 | starting | show processlist |

+-----+------+-----------+--------+---------+------+---------------------------------+-----------------------------------+

3 rows in set (0.00 sec)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

# SESSION A

mysql>begin;

mysql>select*fromsbtest2;

# SESSION B

mysql>altertablesbtest2addtest2int;//等待SESSIONA执行完;

# SESSION C

mysql>showprocesslist;

+-----+------+-----------+--------+---------+------+---------------------------------+-----------------------------------+

|Id|User|Host|db|Command|Time|State|Info|

+-----+------+-----------+--------+---------+------+---------------------------------+-----------------------------------+

|267|root|localhost|sbtest|Sleep|36||NULL|

|271|root|localhost|sbtest|Query|30|Waitingfortablemetadatalock|altertablesbtest2addtest2int|

|272|root|localhost|NULL|Query|0|starting|showprocesslist|

+-----+------+-----------+--------+---------+------+---------------------------------+-----------------------------------+

3rowsinset(0.00sec)

d)表上有失败的查询事务,比如查询不存在的列,语句失败返回,但是事务没有提交,此时alter仍然会被堵住。

# SESSION A

mysql> begin;

mysql> select error from sbtest2;

ERROR 1054 (42S22): Unknown column 'error' in 'field list'

# SESSION B

mysql> alter table sbtest2 add test3 int; //等待SESSION A提交或回滚;

# SESSION C

mysql> show processlist;

+-----+------+-----------+--------+---------+------+---------------------------------+-----------------------------------+

| Id | User | Host | db | Command | Time | State | Info |

+-----+------+-----------+--------+---------+------+---------------------------------+-----------------------------------+

| 267 | root | localhost | sbtest | Sleep | 7 | | NULL |

| 271 | root | localhost | sbtest | Query | 3 | Waiting for table metadata lock | alter table sbtest2 add test3 int |

| 272 | root | localhost | NULL | Query | 0 | starting | show processlist |

| 311 | root | localhost | NULL | Sleep | 413 | | NULL |

+-----+------+-----------+--------+---------+------+---------------------------------+-----------------------------------+

4 rows in set (0.00 sec)

# SESSION D

mysql> select * from information_schema.innodb_trx;

Empty set (0.00 sec)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

# SESSION A

mysql>begin;

mysql>selecterrorfromsbtest2;

ERROR1054(42S22):Unknowncolumn'error'in'field list'

# SESSION B

mysql>altertablesbtest2addtest3int;//等待SESSIONA提交或回滚;

# SESSION C

mysql>showprocesslist;

+-----+------+-----------+--------+---------+------+---------------------------------+-----------------------------------+

|Id|User|Host|db|Command|Time|State|Info|

+-----+------+-----------+--------+---------+------+---------------------------------+-----------------------------------+

|267|root|localhost|sbtest|Sleep|7||NULL|

|271|root|localhost|sbtest|Query|3|Waitingfortablemetadatalock|altertablesbtest2addtest3int|

|272|root|localhost|NULL|Query|0|starting|showprocesslist|

|311|root|localhost|NULL|Sleep|413||NULL|

+-----+------+-----------+--------+---------+------+---------------------------------+-----------------------------------+

4rowsinset(0.00sec)

# SESSION D

mysql>select*frominformation_schema.innodb_trx;

Emptyset(0.00sec)

其实SESSION A中的事务并未开启,但是由于select获取表元数据的语句,语法上是有效的,虽然执行失败了,但是任然不会释放元数据锁,故而导致SESSION B的alter动作被阻塞。

通过SESSION D查看当前打开事务时,你会发现没有,从而找不到原因。所以当出现这种场景时,如何判断是哪个进程导致的呢,我们可以尝试查看表performance_schema. events_statements_current,分析进程状态来进行判断。

mysql> select * from performance_schema. events_statements_current\G

*************************** 1. row ***************************

THREAD_ID: 293

EVENT_ID: 32

END_EVENT_ID: 32

EVENT_NAME: statement/sql/select

SOURCE: socket_connection.cc:101

TIMER_START: 212721717099954000

TIMER_END: 212721717213807000

TIMER_WAIT: 113853000

LOCK_TIME: 0

SQL_TEXT: select error from sbtest2

DIGEST: 0bbb2d5d1be45e77debea68111264885

DIGEST_TEXT: SELECT ERROR FROM `sbtest2`

CURRENT_SCHEMA: sbtest

OBJECT_TYPE: NULL

OBJECT_SCHEMA: NULL

OBJECT_NAME: NULL

OBJECT_INSTANCE_BEGIN: NULL

MYSQL_ERRNO: 1054

RETURNED_SQLSTATE: 42S22

MESSAGE_TEXT: Unknown column 'error' in 'field list'

ERRORS: 1

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

mysql>select*fromperformance_schema.events_statements_current\G

***************************1.row***************************

THREAD_ID:293

EVENT_ID:32

END_EVENT_ID:32

EVENT_NAME:statement/sql/select

SOURCE:socket_connection.cc:101

TIMER_START:212721717099954000

TIMER_END:212721717213807000

TIMER_WAIT:113853000

LOCK_TIME:0

SQL_TEXT:selecterrorfromsbtest2

DIGEST:0bbb2d5d1be45e77debea68111264885

DIGEST_TEXT:SELECTERRORFROM`sbtest2`

CURRENT_SCHEMA:sbtest

OBJECT_TYPE:NULL

OBJECT_SCHEMA:NULL

OBJECT_NAME:NULL

OBJECT_INSTANCE_BEGIN:NULL

MYSQL_ERRNO:1054

RETURNED_SQLSTATE:42S22

MESSAGE_TEXT:Unknowncolumn'error'in'field list'

ERRORS:1

然后找到其sid, kill掉该session,也可以kill掉DDL所在的session解决可以解决此问题。

另外,测试时SESSION A要显式开启一个事务,否则查询会隐式回滚结束,无法重现上面的场景。SESSION B执行alter后,没有立即阻塞住,而是立马开始copy to tmp table,这个过程结束后,才进行了MDL锁等待。这怎么解释呢,应该是执行alter操作主要分为创建临时新表->插入老表的数据->临时新表rename to老表三个步骤,在这种情况下,到最后一步才需要MDL锁,所以copy过程中不会阻塞。由于没有查询在进行,而且查询也没有进入innodb层 (失败返回),所以show processlist和information_schema.innodb_trx没有可以参考的信息。

出现以上几种情况时,这个时候如果进行如下操作就会引起MDL:

创建、删除索引。

修改表结构。

表维护操作(optimize table、repair table等)。

删除表。

获取表上表级写锁 (lock table tab_name write)。

使用Profile分析场景三:显示或者隐式开启事务后未提交或回滚,比如查询完成后未提交或者回滚,使用alter会被堵住

# SESSION A

mysql> set profiling=on;

mysql> begin;

mysql> select * from sbtest.sbtest2 limit 1;

# SESSION B

mysql> set profiling=on;

mysql> alter table sbtest.sbtest5 add test2 int; //等待SESSION A执行完;

# SESSION C

mysql> set profiling=on;

mysql> select * from sbtest.sbtest2 limit 1;

# SESSION D

mysql> set profiling=on;

mysql> show processlist;

+-----+------+-----------+------+---------+------+---------------------------------+------------------------------------------+

| Id | User | Host | db | Command | Time | State | Info |

+-----+------+-----------+------+---------+------+---------------------------------+------------------------------------------+

| 325 | root | localhost | NULL | Query | 25 | Waiting for table metadata lock | alter table sbtest.sbtest2 add test5 int |

| 326 | root | localhost | NULL | Query | 3 | Waiting for table metadata lock | select * from sbtest.sbtest2 limit 1 |

| 327 | root | localhost | NULL | Query | 0 | starting | show processlist |

| 328 | root | localhost | NULL | Sleep | 50 | | NULL |

+-----+------+-----------+------+---------+------+---------------------------------+------------------------------------------+

4 rows in set (0.00 sec)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

# SESSION A

mysql>setprofiling=on;

mysql>begin;

mysql>select*fromsbtest.sbtest2limit1;

# SESSION B

mysql>setprofiling=on;

mysql>altertablesbtest.sbtest5addtest2int;//等待SESSIONA执行完;

# SESSION C

mysql>setprofiling=on;

mysql>select*fromsbtest.sbtest2limit1;

# SESSION D

mysql>setprofiling=on;

mysql>showprocesslist;

+-----+------+-----------+------+---------+------+---------------------------------+------------------------------------------+

|Id|User|Host|db|Command|Time|State|Info|

+-----+------+-----------+------+---------+------+---------------------------------+------------------------------------------+

|325|root|localhost|NULL|Query|25|Waitingfortablemetadatalock|altertablesbtest.sbtest2addtest5int|

|326|root|localhost|NULL|Query|3|Waitingfortablemetadatalock|select*fromsbtest.sbtest2limit1|

|327|root|localhost|NULL|Query|0|starting|showprocesslist|

|328|root|localhost|NULL|Sleep|50||NULL|

+-----+------+-----------+------+---------+------+---------------------------------+------------------------------------------+

4rowsinset(0.00sec)

然后回滚SESSION A,等待SESSION B和SESSION C执行完,查看profile。

查看SESSION A

# SESSION A

mysql> show profiles;

+----------+-------------+------------------------------------------+

| Query_ID | Duration | Query |

+----------+-------------+------------------------------------------+

| 1 | 42.81646375 | alter table sbtest.sbtest2 add test5 int |

+----------+-------------+------------------------------------------+

1 row in set, 1 warning (0.00 sec)

mysql> show profile for query 1;

+--------------------------------+----------+

| Status | Duration |

+--------------------------------+----------+

| starting | 0.000069 |

| checking permissions | 0.000004 |

| checking permissions | 0.000004 |

| init | 0.000005 |

| Opening tables | 0.000128 |

| setup | 0.000032 |

| creating table | 0.000654 |

| After create | 0.000053 |

| Waiting for table metadata loc | 1.000069 |

.....................

| After create | 0.000013 |

| Waiting for table metadata loc | 0.871435 |

| After create | 0.000042 |

| System lock | 0.000013 |

| preparing for alter table | 0.002475 |

| altering table | 9.752928 |

| committing alter table to stor | 0.185624 |

| end | 0.000021 |

| query end | 0.000010 |

| closing tables | 0.000007 |

| freeing items | 0.000020 |

| cleaning up | 0.000011 |

+--------------------------------+----------+

83 rows in set, 1 warning (0.00 sec)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

# SESSION A

mysql>showprofiles;

+----------+-------------+------------------------------------------+

|Query_ID|Duration|Query|

+----------+-------------+------------------------------------------+

|1|42.81646375|altertablesbtest.sbtest2addtest5int|

+----------+-------------+------------------------------------------+

1rowinset,1warning(0.00sec)

mysql>showprofileforquery1;

+--------------------------------+----------+

|Status|Duration|

+--------------------------------+----------+

|starting|0.000069|

|checkingpermissions|0.000004|

|checkingpermissions|0.000004|

|init|0.000005|

|Openingtables|0.000128|

|setup|0.000032|

|creatingtable|0.000654|

|Aftercreate|0.000053|

|Waitingfortablemetadataloc|1.000069|

.....................

|Aftercreate|0.000013|

|Waitingfortablemetadataloc|0.871435|

|Aftercreate|0.000042|

|Systemlock|0.000013|

|preparingforaltertable|0.002475|

|alteringtable|9.752928|

|committingaltertabletostor|0.185624|

|end|0.000021|

|queryend|0.000010|

|closingtables|0.000007|

|freeingitems|0.000020|

|cleaningup|0.000011|

+--------------------------------+----------+

83rowsinset,1warning(0.00sec)

查看SESSION C

mysql> show profiles;

+----------+-------------+--------------------------------------+

| Query_ID | Duration | Query |

+----------+-------------+--------------------------------------+

| 1 | 10.75216050 | select * from sbtest.sbtest2 limit 1 |

+----------+-------------+--------------------------------------+

1 row in set, 1 warning (0.00 sec)

mysql> show profile for query 1;

+--------------------------------+-----------+

| Status | Duration |

+--------------------------------+-----------+

| starting | 0.000080 |

| checking permissions | 0.000007 |

| Opening tables | 0.000012 |

| Waiting for table metadata loc | 10.751829 |

| Opening tables | 0.000094 |

| init | 0.000019 |

| System lock | 0.000010 |

| optimizing | 0.000004 |

| statistics | 0.000011 |

| preparing | 0.000009 |

| executing | 0.000003 |

| Sending data | 0.000040 |

| end | 0.000006 |

| query end | 0.000008 |

| closing tables | 0.000008 |

| freeing items | 0.000014 |

| cleaning up | 0.000009 |

+--------------------------------+-----------+

17 rows in set, 1 warning (0.00 sec)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

mysql>showprofiles;

+----------+-------------+--------------------------------------+

|Query_ID|Duration|Query|

+----------+-------------+--------------------------------------+

|1|10.75216050|select*fromsbtest.sbtest2limit1|

+----------+-------------+--------------------------------------+

1rowinset,1warning(0.00sec)

mysql>showprofileforquery1;

+--------------------------------+-----------+

|Status|Duration|

+--------------------------------+-----------+

|starting|0.000080|

|checkingpermissions|0.000007|

|Openingtables|0.000012|

|Waitingfortablemetadataloc|10.751829|

|Openingtables|0.000094|

|init|0.000019|

|Systemlock|0.000010|

|optimizing|0.000004|

|statistics|0.000011|

|preparing|0.000009|

|executing|0.000003|

|Sendingdata|0.000040|

|end|0.000006|

|queryend|0.000008|

|closingtables|0.000008|

|freeingitems|0.000014|

|cleaningup|0.000009|

+--------------------------------+-----------+

17rowsinset,1warning(0.00sec)

从上述测试可以看出,SESSION C需要打开表时碰到了元数据锁。MySQL不论SESSION A执行的是select还是delete,此时alter table语句无法获取到metadata独占锁,会进行等待;所以会影响SESSION C的读取。

这是最基本的一种情形,这个和MySQL 5.6中的online ddl并不冲突。一般alter table的操作过程中,在after create步骤会获取metadata独占锁,当进行到altering table的过程时(通常是最花时间的步骤),对该表的读写都可以正常进行,这就是online ddl的表现,并不会像之前在整个alter table过程中阻塞写入,当然并不是所有ALTER语句都支持online ddl。

总之,alter table的语句是很危险的(其实他的危险其实是未提交事物或者长事务导致的),在操作之前最好确认对要操作的表没有任何进行中的操作、没有未提交事务、也没有显式事务中的报错语句。如果有alter table的维护任务,在无人监管的时候运行,最好通过lock_wait_timeout设置好超时时间,避免长时间的metedata锁等待。

2. Waiting for global read lock

构造一个Waiting for global read lock场景:

session1: alter table t1 add c3 bigint; //大表执行需较长时间

session2: set global read only=on; //等待

1

2

session1:altertablet1addc3bigint;//大表执行需较长时间

session2:setglobalreadonly=on;//等待

查看进行信息:

mysql> show processlist;

+----+------+-----------------+------+---------+------+------------------------------+------------------+

| Id | User | Host | db | Command | Time | State | Info |

+----+------+-----------------+------+---------+------+------------------------------+------------------+

| 1 | root | localhost:5202 | test | Query | 12 | altering table | alter table t1 add c3 bigint |

| 2 | root | localhost:14699 | test | Query | 3 | Waiting for global read lock | set global read_only=on |

| 3 | root | localhost:17085 | NULL | Query | 0 | init | show processlist |

+----+------+-----------------+------+---------+------+------------------------------+------------------+

1

2

3

4

5

6

7

8

mysql>showprocesslist;

+----+------+-----------------+------+---------+------+------------------------------+------------------+

|Id|User|Host|db|Command|Time|State|Info|

+----+------+-----------------+------+---------+------+------------------------------+------------------+

|1|root|localhost:5202|test|Query|12|alteringtable|altertablet1addc3bigint|

|2|root|localhost:14699|test|Query|3|Waitingforglobalreadlock|setglobalread_only=on|

|3|root|localhost:17085|NULL|Query|0|init|showprocesslist|

+----+------+-----------------+------+---------+------+------------------------------+------------------+

分析:

alter table t1 add c3 bigint; 会加(GLOBAL,MDL_STATEMENT,MDL_INTENTION_EXCLUSIVE) 语句结束后才释放

set global read only=on; 会加(GLOBAL,MDL_EXPLICIT,MDL_SHARED)

由于session1执行时间比较长,一直持有MDL_INTENTION_EXCLUSIVE。从兼容性矩阵可以看出MDL_SHARED和MDL_INTENTION_EXCLUSIVE是不相容的,因此发生“Waiting for global read lock ”等待。直到session 1 alter操作完成释放MDL_INTENTION_EXCLUSIVE。set global read only=on;才可以继续执行。

3. Waiting for commit lock

构造一个Waiting for commit lock场景:

session1: begin; insert into t1 vlaues(null, 'ab');

session2: flush table with read lock; //成功

session1: commit //发生等待

1

2

3

session1:begin;insertintot1vlaues(null,'ab');

session2:flushtablewithreadlock;//成功

session1:commit//发生等待

查看进行信息:

mysql> show processlist;

+----+------+-----------------+------+------------+------+-------------------------+------------------+

| Id | User | Host | db | Command | Time | State | Info |

+----+------+-----------------+------+------------+------+-------------------------+------------------+

| 1 | root | 127.0.0.1:5202 | test | Query | 7 | Waiting for commit lock | commit |

| 2 | root | 127.0.0.1:14699 | test | Sleep | 13 | | NULL |

| 3 | root | 127.0.0.1:17085 | NULL | Query | 0 | init | show processlist |

+----+------+-----------------+------+---------+------+-------------------------+------------------+

1

2

3

4

5

6

7

8

mysql>showprocesslist;

+----+------+-----------------+------+------------+------+-------------------------+------------------+

|Id|User|Host|db|Command|Time|State|Info|

+----+------+-----------------+------+------------+------+-------------------------+------------------+

|1|root|127.0.0.1:5202|test|Query|7|Waitingforcommitlock|commit|

|2|root|127.0.0.1:14699|test|Sleep|13||NULL|

|3|root|127.0.0.1:17085|NULL|Query|0|init|showprocesslist|

+----+------+-----------------+------+---------+------+-------------------------+------------------+

分析:

flush table with read lock; 持有(COMMIT,MDL_EXPLICIT,MDL_SHARED)

commit; 时上(COMMIT,MDL_EXPLICIT,MDL_INTENTION_EXCLUSIVE)锁,MDL_SHARED和MDL_INTENTION_EXCLUSIVE是不相容的,因此发生等待.。

四、MDL锁的性能与并发改进

讲到这会发现MDL锁的开销并不比InnoDB层的行锁要小,而且这可能是一个更为密集的并发瓶颈。MySQL 5.6和5.5版本通常通过调整如下两个参数来进行并发调优:

metadata_locks_cache_size:MDL锁的缓存大小。

metadata_locks_hash_instances:通过分片来提高并发度,与InnoDB AHI类似。

MySQL 5.7 MDL锁的最大改进之处在于将MDL锁的机制通过lock free算法来实现,从而提高了在多核并发下数据库的整体性能提升。

五、MDL锁的诊断

MySQL 5.7版本之前并没有提供一个方便的途径来查看MDL锁,github上有一名为mysql-plugin-mdl-info的项目,通过插件的方式来查看,非常有想法的实现。好在官方也意识到了这个问题,于是在MySQL 5.7中的performance_schea库下新增了一张表metadata_locks,用其来查看MDL锁那是相当的方便:

mysql> SELECT * FROM performance_schema.metadata_locks;

1

mysql>SELECT*FROMperformance_schema.metadata_locks;

不过默认并没有打开此功能,可通过setup_instruments表查看。

mysql> SELECT * FROM performance_schema.setup_instruments;

1

mysql>SELECT*FROMperformance_schema.setup_instruments;

需要手工将wait/lock/metadata/sql/mdl监控给打开:

mysql> UPDATE performance_schema.setup_consumers SET ENABLED = 'YES' WHERE NAME ='global_instrumentation';

mysql> UPDATE performance_schema.setup_instruments SET ENABLED = 'YES' WHERE NAME ='wait/lock/metadata/sql/mdl';

mysql> select * from performance_schema.metadata_locks\G

*************************** 1. row ***************************

OBJECT_TYPE: TABLE

OBJECT_SCHEMA: performance_schema

OBJECT_NAME: metadata_locks

OBJECT_INSTANCE_BEGIN: 140388723649872

LOCK_TYPE: SHARED_READ

LOCK_DURATION: TRANSACTION

LOCK_STATUS: GRANTED

SOURCE: sql_parse.cc:5927

OWNER_THREAD_ID: 7738281

OWNER_EVENT_ID: 14

1

2

3

4

5

6

7

8

9

10

11

12

13

14

mysql>UPDATEperformance_schema.setup_consumersSETENABLED='YES'WHERENAME='global_instrumentation';

mysql>UPDATEperformance_schema.setup_instrumentsSETENABLED='YES'WHERENAME='wait/lock/metadata/sql/mdl';

mysql>select*fromperformance_schema.metadata_locks\G

***************************1.row***************************

OBJECT_TYPE:TABLE

OBJECT_SCHEMA:performance_schema

OBJECT_NAME:metadata_locks

OBJECT_INSTANCE_BEGIN:140388723649872

LOCK_TYPE:SHARED_READ

LOCK_DURATION:TRANSACTION

LOCK_STATUS:GRANTED

SOURCE:sql_parse.cc:5927

OWNER_THREAD_ID:7738281

OWNER_EVENT_ID:14

会话1

mysql> lock table xx read;

Query OK, 0 rows affected (0.21 sec)

1

2

mysql>locktablexxread;

QueryOK,0rowsaffected(0.21sec)

会话2:

mysql> SELECT * FROM performance_schema.metadata_locks\G;

*************************** 1. row ***************************

OBJECT_TYPE: TABLE

OBJECT_SCHEMA: performance_schema

OBJECT_NAME: metadata_locks

OBJECT_INSTANCE_BEGIN: 240554768

LOCK_TYPE: SHARED_READ

LOCK_DURATION: TRANSACTION

LOCK_STATUS: GRANTED

SOURCE: sql_parse.cc:5927

OWNER_THREAD_ID: 38

OWNER_EVENT_ID: 10

1 row in set (0.00 sec)

1

2

3

4

5

6

7

8

9

10

11

12

13

mysql>SELECT*FROMperformance_schema.metadata_locks\G;

***************************1.row***************************

OBJECT_TYPE:TABLE

OBJECT_SCHEMA:performance_schema

OBJECT_NAME:metadata_locks

OBJECT_INSTANCE_BEGIN:240554768

LOCK_TYPE:SHARED_READ

LOCK_DURATION:TRANSACTION

LOCK_STATUS:GRANTED

SOURCE:sql_parse.cc:5927

OWNER_THREAD_ID:38

OWNER_EVENT_ID:10

1rowinset(0.00sec)

如果您觉得本站对你有帮助,那么可以支付宝扫码捐助以帮助本站更好地发展,在此谢过。

mysql MDL锁如何解决_理解MySQL的MDL元数据锁相关推荐

  1. mysql的分区技术作用_理解MySQL数据库分区管理的技术细节

    在MySQL数据库中,表的不同部分在不同的位置被存储为单独的表.分区主要就是用来解决表在不同的位置存储的问题.在其他数据库中,也会存在这种情况.他们将这种类型的数据表称之为分区表.分区的管理,对于My ...

  2. mysql内部实现原理面试_理解完这些基本上能解决面试中MySql的事务问题

    事务是指逻辑上的一组操作,要么都执行,要么都不执行, 事务的特性(ACID)原子性(Atomicity):事务是不可分割的工作单元,要么都成功,要么都失败, 如果事务中一个sql语句执行失败,则已执行 ...

  3. mysql事务最好别用_理解完这些基本上能解决面试中MySql的事务问题

    前言 在面试中,基本上都会问到关于数据库的事务问题,如果啥都不会或者只回答到表面的上知识点的话,那面试基本上是没戏了,为了能顺利通过面试,那MySql的事务问题就需要了解,所以就根据网上的资料总结一版 ...

  4. mysql 锁机制 mvcc_轻松理解MYSQL MVCC 实现机制

    轻松理解MYSQL MVCC 实现机制 轻松理解MYSQL MVCC 实现机制 #### 1. MVCC简介 ##### 1.1 什么是MVCC MVCC是一种多版本并发控制机制. ##### 1.2 ...

  5. mysql隐式锁定辅助索引_当Mysql - InnoDB行锁遇到复合主键和多列索引-Go语言中文社区...

    背景 今天在配合其他项目组做系统压测,过程中出现了偶发的死锁问题.分析代码后发现有复合主键的update情况,更新复合主键表时只使用了一个字段更新,同时在事务内又有对该表的insert操作,结果出现了 ...

  6. mysql死锁释放时间参数_【Mysql】mysql 事务未提交导致死锁 Lock wait timeout exceeded; try restarting transaction 解决办法...

    问题场景 问题出现环境: 1.在同一事务内先后对同一条数据进行插入和更新操作: 2.多台服务器操作同一数据库: 3.瞬时出现高并发现象: 不断的有一下异常抛出,异常信息: org.springfram ...

  7. mysql锁申请步骤_大话MySQL锁

    一.锁介绍 不同存储引擎支持的锁是不同的,比如MyISAM只有表锁,而InnoDB既支持表锁又支持行锁. 下图展示了InnoDB不同锁类型之间的关系: 图中的概念比较多不好理解,下面依次进行说明. 1 ...

  8. mysql索引优化是什么意思_理解MySQL——索引与优化

    写在前面:索引对查询的速度有着至关重要的影响,理解索引也是进行数据库性能调优的起点.考虑如下情况,假设数据库中一个表有10^6条记录,DBMS的页面大小为4K,并存储100条记录.如果没有索引,查询将 ...

  9. mysql memory 索引_理解MySQL——索引与优化

    索引对查询的速度有着至关重要的影响,理解索引也是进行数据库性能调优的起点.考虑如下情况,假设数据库中一个表有10^6条记录,DBMS的页面大小为4K,并存储100条记录.如果没有索引,查询将对整个表进 ...

最新文章

  1. Python压缩目录文件夹,解压目录文件夹及耗时效率统计
  2. (4)关于Alpha通道问题
  3. vim自定义配置之autoComplPop设置
  4. Java中的subList方法
  5. python爬虫教程视频成都工业学院-成都工业大学:Python爬虫和数据可视化实训课程(2020年3月新版)...
  6. java+线程安全的hash,多线程下HashMap安全问题-ConcurrentHashMap解析
  7. jquery层次选择器:空格 > next + nextAll ~ siblings
  8. python 模拟键盘_Python模拟键盘输入和鼠标操作
  9. mysql中文乱码过滤_记一次mysql中文字符乱码的问题排查
  10. CC2540蓝牙开发一BLE例程
  11. 计算机历史浩荡几十载,细数那些影响时代的人和事
  12. TP6微信公众号登陆授权
  13. c语言 实验报告:分支结构程序设计
  14. 【干货】消费金融助贷业务实操全速览
  15. NI LabVIEW 2014SP1视觉开发必备软件和工具包
  16. 英特尔新处理器i7-8559U在3DMark数据库中现身
  17. 群晖导入SSL证书HTTPS访问
  18. 织梦配置多个mysql_织梦教程:DEDECMS中MYSQL修复表的两个小技巧
  19. .md是什么文件_生信中常见的数据文件格式
  20. IT界6个国内技术大牛博客,全栈工程师修行的秘籍!

热门文章

  1. pytorch学习(一)pytorch中的断点续训
  2. 西门子触摸屏HMI变量的指针化索引应用示例详解
  3. css绘制自定义数据仪表盘
  4. 关于如何用大数据做“用户画像”调查报告(持续更新)
  5. [分享] - (开源)PHP高级境界-网络操作系统(eyeOS)
  6. [羊城杯2020]easyphp
  7. 发现一个不错的源码搜索网站
  8. 行为识别 - ACTION-Net: Multipath Excitation for Action Recognition
  9. 何恺明 MIT 最新演讲:未来工作将聚焦 AI for science
  10. VS2010如何修改项目名称