本文已参与「新人创作礼」活动, 一起开启掘金创作之路。
参考:
一、Online DDL 产生背景:
随着业务的不断迭代和变更,对于线上环境所依赖的MySQL表结构等经常也会随之变化。
日常工作中常常有一些DDL变更,MySQL5.6 之前这类操作通常会锁表,所以很多线上服务升级等都会选择避开流量高峰,避免影响线上业务,引起用户工单。甚至当服务更新周期较长时,会选择停服。
因此在 MySQL5.6 之后上线的Online DDL新特性解决了执行DDL锁表的问题:保证了在进行表变更时,不会堵塞线上业务读写,数据库依然能正常对外提供服务。
- DDL(Data Definition Languages):数据定义语言。这些语句定义了不同的数据段、数据库、表、列、索引等数据库对象的定义。常用的语句关键字主要包括
create、drop、alter ....等。- DML(Data Control Language):数据控制语句,用于控制不同数据段直接的许可和访问级别的语句。这些语句定义了数据库、表、字段、用户的访问权限和安全级别。主要的语句关键字包括
grant、revoke等。
二、DDL实现方式及原理:
在MySQL5.6 Online DDL 推出之前,执行 DDL 主要有两种方式:copy 和 inplace 方式。 inplace方式又称为 fast index creation。相对于 copy 方式,inplace 方式无需拷贝数据,因此较快,但仅支持添加、删除索引两种方式,且与copy方式一样需要全程锁表,实用性不是很强。下面以加索引为例,简单介绍这两种方式的实现流程:
- copy 方式:
- 新建带索引的临时表
- 锁原表,禁止DML,允许查询
- 将原表数据拷贝到临时表(无排序,一行一行拷贝)
- 进行rename,升级字典锁,禁止读写
- 完成创建索引操作
- inplace 方式:
- 新建索引的数据字典
- 锁表,禁止DML,允许查询
- 读取聚集索引,构造新的索引项,排序并插入新索引
- 等待打开当前表的所有只读事务提交
- 创建索引结束
三、Online DDL 实现
Online DDL 方式实质也包含了copy和inplace方式
- 对于不支持Online DDL操作采用 copy 方式,比如:删除主键,修改列类型,修改字符集 等,这些操作都会导致记录格式发生变化,无法通过简单的 全量+增量 的方式实现Online DDL;
- 对于支持Online DDL操作采用 inplace 方式,mysql内部以 是否修改行记录格式 为准分两类:
- rebuild 方式:若修改行记录格式,需重建表(重新组织记录)。比如 OPTIMIZE优化表、添加索引、添加/删除字段、修改格式、修改列NULL/NOT NULL属性 等;
- no-rebuild 方式:若无需修改行记录格式,只需要修改表的元数据就无需创建表。比如 添加/删除索引、修改列名、修改字段默认值、修改列自增值、修改字段名/表名 等。
Online DDL 主要包括3个阶段,Prepare阶段 → DDL 执行阶段 → Commit阶段,流程分别如下:
Prepare 阶段:
- 持有 EXCLUSIVE-MDL 锁,禁止DML语句读写
- 根据DDL类型,确定执行方式(Copy,Online-rebuild,Online-no-rebuild)
- 创建新的 frm 和 ibd 临时文件(ibd临时文件仅rebuild类型需要)
- 分配 row_log 空间,用来记录 DDL Execute 阶段产生的DML操作(仅rebuild类型需要)
DDL 执行阶段:
- 降级 EXCLUSIVE-MDL 锁,允许DML语句读写
- 扫描原表主键以及二级索引的所有数据页,生成B+树,存储到临时文件中
- 将 DDL Execute 阶段产生的DML操作记录到 row_log(仅rebuild类型需要)
Commit 阶段
- 升级到 EXCLUSIVE-MDL 锁,禁止DML语句读写
- 将 row_log 中记录的DML操作应用到临时文件,得到一个逻辑数据上与原表相同的数据文件(仅rebuild类型需要)
- 重命名 frm 和 idb 临时文件,替换原表,将原表文件删除
- 提交事务(刷事务的redo日志),变更完成
由上面的流程可知,Prepare阶段和Commit阶段都禁止读写(保证数据一致性),只有Execute允许读写,那为什么说Online DDL 方式在执行过程中可以对表中数据进行读写操作? 其实是因为 Prepare阶段 和 Commit阶段 相对于 DDL执行阶段 时间特别短,因此基本可以认为是全程允许读写的。
四、建议:
- 尽量不要在线上环境 - 业务高峰期间进行DDL,即使是online DDL;
- 对于大表的 DDL 变更,尽量在测试库上预先执行一遍(需保证数据量相当),预估下变更所花费的时间,避免线上环境出问题。
五、常见问题
1、如何实现数据完整性 使用online ddl后,用户心中一定有一个疑问,一边做ddl,一边做dml,表中的数据不会乱吗?这里面关键部件是row_log。row_log记录了ddl变更过程中新产生的dml操作,并在ddl执行的最后将其应用到新的表中,保证数据完整性。
2、online 与数据一致性如何兼得(全量+增量) 实际上,online ddl并非整个过程都是online,在prepare阶段和commit阶段都会持有MDL-Exclusive锁,禁止读写;而在整个ddl执行阶段,允许读写。由于prepare和commit阶段相对于ddl执行阶段时间特别短,因此基本可以认为是全程online的。Prepare阶段和commit阶段的禁止读写,主要是为了保证数据一致性。Prepare阶段需要生成row_log对象和修改内存的字典;Commit阶段,禁止读写后,重做最后一部分增量,然后提交,保证数据一致。
3、如何实现 server 层和 innodb 层一致性 在prepare阶段,server层会生成一个临时的frm文件,里面包含了新表的格式;innodb层生成了临时的ibd文件(rebuild方式);在ddl执行阶段,将数据从原表拷贝到临时ibd文件,并且将row_log增量应用到临时ibd文件;在commit阶段,innodb层修改表的数据字典,然后提交;最后innodb层和mysql层面分别重命名frm和idb文件。
4、对 innodb 表做 ddl 过程中异常了,为啥再次做 ddl 报 #sql-xxx already exists 原理 MySQL 5.7 以上的版本中,在执行创建或者删除的操作同时,将DML操作日志写入一个缓存中。待修改完成之后再重做到原表上,以保住数据的一致性。这个缓存大小由
innodb_online_alter_log_max_size控制,默认为 128MB,若用户更改表比较频繁,在线 DML 业务压力较大,则 innodb_online_alter_log_max_size 空间不能存放日志,会抛出错误,此时可以调大innodb_online_alter_log_max_size 获得更多日志缓存空间解决问题 。
更多关于Online DDL 的语法与可选参数可参考:MySQL修改表结构到底会不会锁表?-阿里云开发者社区