更改mysql表结构会锁表吗

在日常的需求开发,不可避免的给表增加字段,但是或多或少都听说过在使用alter table的时候会导致mysql锁表。表中的数据少还好,但是表中数据多的情况下修改表结构可能需要数个小时,那么如果真的锁表,无法读写是不能接受的。

修改表结构为什么这么慢

MYSQL的ALTER TABLE操作的性能对大表来说是个大问题。MYSQL执行大部分修改表结构操作的方法是用新的表结构创建一个空表,从旧表中查出所有数据插入新表,然后删除旧表。这样操作可能需要花费很长时间,如果内存不足而表又很大,而且还有很多索引的情况下尤其如此。许多人都有这样的经验,ALTER TABLE操作需要花费数个小时甚至数天才能完成。

DDL和DML

DML,Data Manipulation Language,数据操纵语言。适用范围:对数据库中的数据进行一些简单操作,如insert、delete、update、select等。
DDL,Data Definition Language,数据定义语言。适用范围:对数据库中的某些对象(例如database、table)进行管理,如Create、Alter和Drop。
DML操作是可以手动控制事务的开启、提交和回滚的。DDL操作是隐性提交的,不能rollback!

元数据锁(MDL)

当我们在 MySQL 中执行 DDL 语句时,经常会发现语句没有在你预期的时间完成,这时候我们通常会在mysql输入终端输入show full processlist ,来看看发生了什么状况。当你看到 waiting for table metadata lock 时,那就碰到元数据锁了

什么是元数据锁(metadata lock)

举一个简单的例子,如果你在查询一个表的过程中,另外一个session对该表删除了一个列,那前面的查询到底该显示什么呢?如果在RR隔离级别下,事物中再次执行相同的语句还会和之前结果一致吗?
答案是否定的,RR隔离级别下,会话A在2次查询期间,会话B对表结构做了修改,两次查询结果就会不一致,无法满足可重复读的要求。
为了防止这种情况,所以在5.5.3版本引入了MDL锁(表查询开始MySQL会在表上加一个锁,来防止被别的session修改了表定义,)。
其实5.5也有类似保护元数据的机制,只是没有明确提出MDL概念而已。但是5.5之前版本(比如5.1)与5.5之后版本在保护元数据这块有一个显著的不同点是,5.1对于元数据的保护是语句级别的,5.5对于Metadata的保护是事务级别的。所谓语句级别,即语句执行完成后,无论事务是否提交或回滚,其表结构可以被其他会话更新;而事务级别则是在事务结束后才释放MDL。引入MDL锁主要是为了解决这个问题:
事务隔离问题:比如在可重复隔离级别下,会话A在2次查询期间,会话B对表结构做了修改,两次查询结果就会不一致,无法满足可重复读的要求。
DDL操作与MetaData Lock
Metadata lock 机制是为了保证数据一致性存在的,在有session操作时候某张表时,需要首先获得Metadata lock ,然后操作,如果这个时候,又来了一个session要DDL操作同一个表,就会出现 waiting for table metadata lock
出现这个锁会导致所该表有读写操作都被阻塞,出现这个问题会十分严重

metadata lock和行锁有什么区别?

元数据锁是server层的锁,表级锁,每执行一条DML、DDL语句时都会申请metadata锁DML操作需要metadata读锁锁,DDL操作需要metadata写锁,metadata加锁过程是系统自动控制,无法直接干预,读锁和写锁的阻塞关系如下:
读锁和写锁之间相互阻塞,即同一个表上的DML和DDL之间互相阻塞。
写锁和写锁之间互相阻塞,即两个session不能对表同时做表定义变更,需要串行操作。
读锁和读锁之间不会产生阻塞。也就是增删改查不会因为metadata lock产生阻塞,可以并发执行,日常工作中大家看到的DML之间的锁等待是innodb行锁引起的,和metadata lock无关。
申请metadata锁的操作会形成一个队列,队列中写锁获取优先级高于读锁。一旦出现写锁等待,不但当前操作会被阻塞,同时还会阻塞后续该表的所有操作。事务一旦申请到metadata锁锁后,直到事务执行完才会将锁释放。(这里有种特殊情况如果事务中包含DDL操作,mysql会在DDL操作语句执行前,隐式提交commit,以保证该DDL语句操作作为一个单独的事务存在,同时也保证元数据排他锁的释放)。
熟悉innodb行锁的同学这里可能有点疑困惑,因为行锁分类和metadata lock很类似,也主要分为读锁和写锁,或者叫共享锁和排他锁,读写锁之间阻塞关系也一致。二者最重要的区别一个是表锁,一个是行锁,且行锁中的读写操作对应在metadata lock中都属于读锁,修改表结构会给表上mdl写锁,增删改查都会给表上mdl写锁。
注意: 支持事务的InnoDB引擎表和不支持事务的MyISAM引擎表,都会出现Metadata Lock Wait等待现象。一旦出现Metadata Lock Wait等待现象,后续所有对该表的访问都会阻塞在该等待上,导致连接堆积,业务受影响。
medadata lock为什么会造成系统崩溃?
举一个简单例子,session1启动一个事务,对表students执行一个简单的查询;session2对students加一个字段;session3来对students做一个查询;session4来对students做一个update,各个session串行操作。

DDL实现

MySQL5.6 版本以前,执行 DDL 主要有两种方式:Copy 方式In-place 方式

Copy 方式执行DDL操作

  1. 创建与原表结构定义完全相同的临时表
  2. 为原表加MDL(meta data lock,元数据锁)锁,禁止对表中数据进行增删改,允许查询
  3. 在临时表上执行DDL语句
  4. 按照主键 ID 递增的顺序,把数据一行一行地从原表里读出来再插入到临时表中
  5. 升级原表上的锁,禁止对原表中数据进行读写操作
  6. 将原表删除,将临时表重命名为原表名,DDL操作完成

In-place 方式执行DDL操作

In-place 方式 又称为 Fast Index Creation 。与 Copy 方式相比,In-place方式不复制数据,因此大大加快了执行速度。但是这种方式仅支持对二级索引进行添加、删除操作,而且与Copy方式一样需要全程锁表。下面以添加索引为例,简单介绍In-place方式的实现流程:

  1. 创建新索引的数据字典
  2. 为原表加MDL(meta data lock,元数据锁)锁,禁止对表中数据进行增删改,允许查询
  3. 按照聚簇索引的顺序,查询数据,找到需要的索引列数据,排序后插入到新的索引页中
  4. 等待打开当前表的所有只读事务提交
  5. 创建索引结束

Online DDL

MySQL5.6 版本之后加入了 Online DDL 新特性,用于支持DDL执行期间DML语句的并行操作,提高数据库的吞吐量。
与 Copy 方式和 In-place 方式相比,Online 方式在执行DDL的时候可以对表中数据进行读写操作。

MySQL5.6之前要求在整个DDL过程中锁表全程加MDL锁,阻塞更新,也就是说这个DDL不是Online的,如下:
更改mysql表结构会锁表吗_第1张图片

而在MySQL5.6版本开始引入的Online DDL,对这个操作流程做了优化;如下:
更改mysql表结构会锁表吗_第2张图片
上述两个图的不同之处在于,由于日志文件记录和重放操作这个功能的存在,这个方案在重建表的过程中,允许对表A的数据做增删改操作,这也就是OnlineDDL名字的来源;

Online DDL 实现流程(了解)

Online DDL 主要包括3个阶段,Prepare阶段,Execute阶段,Commit阶段。
下面将主要介绍Online DDL执行过程中三个阶段的流程。
Prepare 阶段

  • 持有 EXCLUSIVE-MDL 锁,禁止DML语句读写
  • 根据DDL类型,确定执行方式(Copy,Online-rebuild,Online-no-rebuild)
  • 创建新的 frm 和 ibd 临时文件(ibd临时文件仅rebuild类型需要)
  • 分配 row_log 空间,用来记录 DDL Execute 阶段产生的DML操作(仅rebuild类型需要)

Execute 阶段

  • 降级 EXCLUSIVE-MDL 锁,允许DML语句读写
  • 扫描原表主键以及二级索引的所有数据页,生成B+树,存储到临时文件中
  • 将 DDL Execute 阶段产生的DML操作记录到 row_log(仅rebuild类型需要)

Commit 阶段

  • 升级到 EXCLUSIVE-MDL 锁,禁止DML语句读写
  • 将 row_log 中记录的DML操作应用到临时文件,得到一个逻辑数据上与原表相同的数据文件(仅rebuild类型需要)
  • 重命名 frm 和 idb 临时文件,替换原表,将原表文件删除
  • 提交事务(刷事务的redo日志),变更完成

由上面的流程可知,Prepare阶段和Commit阶段都禁止读写,只有Execute允许读写开始复制数据的时候退化成读锁,不解锁的原因是防止有其他线程尝试获取DML写锁,从而对阻止其它线程修改表结构;最后把row log的数据复制到临时表的时候还会获取一次MDL写锁,防止其他DML操作影响数据的一致性,那为什么说Online DDL 方式在执行过程中可以对表中数据进行读写操作其实是因为Prepare和Commit阶段相对于Execute阶段时间特别短,因此基本可以认为是全程允许读写的。Prepare阶段和Commit阶段都禁止读写,主要是为了保证数据一致性。
需要补充说明的是,重建表时会扫描原表数据和构建临时文件;对于很大的表来说,这个操作是很
消耗IO和CPU资源的
;因此,如果是线上服务,你要很小心地控制操作时间,避免业务高峰;或者使用离线表操作;
更改mysql表结构会锁表吗_第3张图片

Online 和 inplace

对于Online,有另一个跟DDL有关的、容易混淆的概念inplace;其实这两个概念不是描述同一个维度的问题的
在重建表的过程中,如果把表A中的数据导出来的存于tmp_table(第一张图),这是一个临时表,是在MySQL的server层创建的;如果表 A 重建出来的数据是放在“tmp_file”里的(第二张图),这个临时文件是InnoDB在内部创建出来的,server 层来说,没有把数据挪动到临时表,是一个“原地”操作,这就是“inplace”名称的来源;
Online和inplace的概念主要关注这里建"临时表"是通过server还是innoDB,而无论哪种"临时表"都是会占用存储空间的
;举个例子,如果你有一个1TB的表,现在磁盘间是1.2TB,能不能做一个inplace的DDL呢?答案是不能;因为,tmp_file也是要占用临时空间的;
这样说你可能会觉得,inplace跟Online是不是没什么区别?其实不是的,只是在重建表这个逻辑中差不多;如果说这两个逻辑之间的关系是什么的话,可以概括为:

  • DDL过程如果是Online的,就一定是inplace的;
  • 反过来未必,也就是说inplace的DDL,有可能不是Online的;截止到MySQL8.0,添加全文索引(FULLTEXT index)和空间索引(SPATIAL index)就属于这种情况;

Online DDL能避免metadata lock造成的业务阻塞吗

答案是并不完全能。例如A事务开始执行,未提交,那么此时一致占据着MDL的共享读锁,此时执行alter table语句。那么加Online DDL Prepare阶段持有 EXCLUSIVE-MDL 锁就会失败等待,造成metadata lock阻塞所有读写操作。如果事务A很快提交了还好,但是如果是一个长事务,那么就会导致长时间阻塞表的读写,造成事故。

总结

  1. mysql5.6之后支持onlineDDL,对于大部分DDL操作都支持online DDL
  2. 对于不支持online ddl的语句,严禁在线上直接执行
  3. 防止出现MDL锁,因为出现MDL锁会导致表的读写操作无法进行,直接影响线上业务。尽量在数据库不繁忙,没长事务的情况下进行操作。
  4. 对于支持online ddl需要rebuild的操作,重建表时会扫描原表数据和构建临时文件;对于很大的表来说,这个操作是很消耗IO和CPU资源的;因此,如果是线上服务,你要很小心地控制操作时间,避免业务高峰;或者使用离线表操作;
  5. ddl操作往往需要建立临时表,临时文件,需要预留足够的磁盘空间。

你可能感兴趣的:(mysql,mysql,数据库)