[TOC]

> 除了我们经常熟知的共享锁和排他锁外,MySQL数据库还有一种锁叫元数据锁,主要为了保证元数据的一致性,用于处理不同线程操作同一数据对象的同步与互斥问题。

# 一、元数据

所谓元数据,就是表示数据的数据,这些数据五花八门,总之,只要不是我们存储到数据库里的数据记录,大多都可以理解为元数据。

描述数据库的任何数据,作为数据库内容的对立面的都是元数据。因此,列名、数据库名、用户名、版本名以及从`SHOW`语句得到的结果中的大部分字符串是元数据。还包括`INFORMATION_SCHEMA`数据库中的表中的内容,因为定义的那些表存储关于数据库对象的信息。

# 二、元数据锁MDL

`metadata lock`是一种为了保证元数据的一致性,用于处理不同线程操作同一数据对象的同步与互斥问题的锁。

MDL锁是为了解决一个有名的[bug989](https://bugs.mysql.com/bug.php?id=989),所以在5.5.3版本引入了MDL锁。5.1对于元数据的保护是语句级别的,5.5对于Metadata的保护是事务级别的。所谓语句级别,即语句执行完成后,无论事务是否提交或回滚,其表结构可以被其他会话更新;而事务级别则是在事务结束后才释放MDL。引入MDL锁主要是为了解决两个问题:

- 事务隔离问题:比如在可重复隔离级别下,会话A在2次查询期间,会话B对表结构做了修改,两次查询结果就会不一致,无法满足可重复读的要求。

- 数据复制问题:比如会话A执行了多条`update`语句期间,另外一个会话B做了`update`并且先提交,就会导致slave在重做时,先重做`alter`,再重做`update`时就会出现复制错误的现象。

DDL操作与MDL锁

- Metadata Lock 机制是为了保证数据一致性存在的,当先后有多个事务对同一个表操作的时候,并且后一个事务在前一个事务结束前就开始了,这时对表的DDL操作就会阻塞,等待获取DDL锁,直到所有的事务都结束才进行DDL操作。

- 自动提交模式下,单语句就是一个事务,执行完了,事务也就结束了。

- Online DDL是指在`alter table`进行的时候,插入/修改/删除数据的sql语句不会`Waiting for table metadata lock`,在5.6以后的版本有支持。一旦alter table TableA的操作停滞在`Waiting for table metadata lock`的状态,后续对TableA的任何操作(包括读)都无法进行,也会在Opening tables的阶段进入`Waiting for table metadata lock `的队列。

# 三、MDL锁的三种场景

在`alter table`时会发生如下三种场景。

## 1、场景一

- 会话 A 对 booboo 表执行读操作`select *,sleep(60) from booboo;`,正在进行未提交事务。

- 会话 B 对 booboo 表执行在线DDL操作`alter table booboo add q4 int default 0;`。

- 会话 C 对 booboo 表执行隐式读操作`select *,sleep(60) from booboo;`进行等待。

- 会话 D 对 booboo 表执行显示读操作`begin;select * from booboo;`也会进行等待。

通过`show full processlist`可以看到会话 A 对booboo表上正在进行的操作,此时会话 B 的`alter table`语句无法获取到metadata 独占锁,会进行等待,会话 C 和会话 D 都会进行等待,且能从 processlist 表中看到对booboo表的操作。会话A提交事务后或`kill`之后,会话C事务结束,会话D `select`语句执行成功,D事务提交则会话B可执行,否则进入场景2。

- 解决方法

第一种情况,`show full processlist; `定位到长时间未提交的事务kill即可。

## 2、场景二

场景一中回话 D 如果忘记最后的`commit;`, 通过`show processlist`是看不到booboo上有任何操作,但实际上存在有未提交的事务,可以使用`select * from information_schema.innodb_trx`查看到。在事务没有完成之前,booboo的锁不会释放,`alter table`同样获取不到metadata的独占锁,所以会话D要么提交事务、要么回滚、要么被kill,则会话B中的Alter可继续执行。

- 解决方法

如果`show processlist`找不到事务的话,执行 `select * from information_schema.innodb_trx`查找事务,找到线程id,kill掉。

## 3、场景三

如果一个SQL语法上有效,但执行时报错,如,列名不存在,其同样会获取MDL锁,直到事务结束才释放。

在场景一中,如果回话 D 在`select`一个不存在的列名,回话 D 也会获取MDL锁,会话B的`alter`语句同样需要等待。解决方法依然是找到会话线程id,kill掉。

# 四、`lock_wait_timeout`设置

MDL的最大等待时间由`lock_wait_timeout`数决定, `set lock_wait_timeout = 50;` 来设置值,在使用工具进行DDL操作时,这个值就不太合理。事实上,pt-online-schema-change和gh-ost对其就进行了相应的调整,其中,前者60s,后者3s。