(一)什么是MDL锁

元数据锁是server层的锁,表级锁,每执行一条DML、DDL语句时都会申请MDL锁,DML操作 需要MDL读锁,DDL操作需要MDL写锁(MDL加锁过程是系统自动控制,无法直接干预,读读 共享,读写互斥,写写互斥),申请MDL锁的操作会形成一个队列,队列中写锁获取优先级 高于读锁。一旦出现写锁等待,不但当前操作会被阻塞,同时还会阻塞后续该表的所有操作。 事务一旦申请到MDL锁后,直到事务执行完才会将锁释放。(这里有种特殊情况如果事务中 包含DDL操作,mysql会在DDL操作语句执行前,隐式提交commit,以保证该DDL语句操作 作为一个单独的事务存在,同时也保证元数据排他锁的释放)。

(二)为什么引入MDL

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

(三)如何减少DDL的时候产生MDL锁时间

(1)pt-osc

pt-online-schema-change pt-osc工作流程:
1、检查更改表是否有主键或唯一索引,是否有触发器
2、检查修改表的表结构,创建一个临时表,在新表上执行ALTER TABLE语句
3、在源表上创建三个触发器分别对于INSERT UPDATE DELETE操作 触发器来应用DDL执行期间对表所做的DML操作,每种DML操作均对应一个触发器 delete变为delete ignore,update和insert均转换为replace into。
4、从源表拷贝数据到临时表,在拷贝过程中,对源表的更新操作会写入到新建表中(在拷表select时需要进行当前读(lock in shared mode)并与insert组成一个事务, 避免快照读导致增量的delete操作丢失)5、将临时表和源表rename(需要元数据修改锁,需要短时间锁表)
6、删除源表和触发器,完成表结构的修改。

(2)gh-ost

gh-ost工作流程:
1、 gh-ost 首先连接到主库上,根据 alter 语句创建幽灵表_tablename_gho;
2、 然后gh-ost作为一个备库连接到主库上,一边在主库上拷贝已有的数据到幽灵表, 一边从主库上拉取增量数据的 binlog,然后不断的把 binlog 应用回主库幽灵表;
3、 等待全部数据同步完成,进行cut-over,即进行幽灵表和原表切换。cut-over是最后一步, 锁住主库的源表,等待binlog应用完毕,然后替换gh-ost幽灵表为源表。gh-ost在执行中, 会在原本的binlog event里面增加hint和心跳包,用来控制整个流程的进度,检测状态等。

(3)online ddl

大部分情况下是对表进行 optimize table、添加索引、添加/删除列、修改列 NULL/NOT NULL 属性等;mysql的online ddl会使用如下流程进行

Inplace-rebuild方式工作流程:

对于 rebuild 方式实现 Online 是通过缓存 DDL 期间的 DML,待 DDL 完成之后,将 DML 应用到表上来实现的。例如,执行一个 alter table A engine=InnoDB; 重建表的 DDL 其大致流程如下:

  1. 建立一个临时文件,扫描表A主键的所有数据页;
  2. 用数据页中表A的记录生成B+树,存储到临时文件中;
  3. 生成临时文件的过程中,将所有对A的操作记录在一个日志文件(row log)中;
  4. 临时文件生成后,将日志文件中的操作应用到临时文件,得到一个逻辑数据上与表A相同的数据文件;
  5. 用临时文件替换表A的数据文件。

说明:

在 copy 数据到新表期间,在原表上是加的 MDL 读锁(允许 DML,禁止 DDL)

在应用增量期间对原表加 MDL 写锁(禁止 DML 和 DDL)