Innodb早期支持通过copy table跟inplace的方式来执行DDL语句,其原理如下:
inplace在copy table的基础上做了一个较大的改进,则是不需要copy整个表格,只需要在原来的ibd文件上,新建所需要的索引页,这个过程比copy table节约极大的IO资源占用 且 DDL SQL执行速度大大提高,减少了该表格不提供写服务的时长。但是inplace仅支持索引的创建于删除,不支持其他的DDL操作,其他的DDL操作,仍然是copy table方式执行。
对于一个线上业务数据库,无论是copy table方式还是inplace方式,这里仍然有一个明显的弊端:操作期间涉及表格不提供写服务!无法对涉及到表格至下INSERT,UPDATE,DELETE操作,仅支持SELECT。
当表格发生DDL操作,可能会出现该表格数分钟甚至数小时不可访问,性能及响应异常,为了有效改善这个情况,MySQL 在5.6.7版本推出了Online DDL。(本文参考官网5.7版本的文档整理及测试)。
详细可见下图,具体语法情况见第4部分。

那么,新增的Online DDL内部是怎样一个实现原理呢?

有3个阶段:prepare、execute、commit。
这里注意下row-log,它是记录 DDL在执行过程中表格发生数据变更的操作,这样就可以保证执行DDL表格的并发性,在EXCUTE阶段可以正常提供写服务,不发生堵塞,最后把row-log应用到新的表格上即可。
Online DDL可以有效改善DDL期间对数据库的影响:
online ddl过程中发生DML时,会把数据修改情况记录到row-log中,而row-log的大小,则由 innodb_online_alter_log_max_size设定,默认为128M,当表格较大且操作频繁时,做DDL过程,可调大该参数,避免出现1799错误:
该选项用于调整DDL加锁的方式,一共有4个选项。
ALGORITHM选项DDL对数据库性能的影响,很大程度受操作方式影响,比如是否是允许in-place,是否请求COPY操作,是否重建整个表格。比如某个表格,修改或者添加默认值,并不会影响到表格内部的数据,所以1s内就可以完成;添加1个索引,需要几十秒,应为需要新增索引数据页跟修改frm文件,但是不用rebuild表格数据;而修改列的数据类型是,可能需要几分钟甚至更多时间,因为其需要重新Rebuild整个表格,执行期间对CPU,IO及buffer pool大量申请资源。
由DDL引起的INPLACE,COPY,REBUILD,可以通过指定ALGORITHM来选择(注意并非所有DDL都支持in-place,详见第4部分)
ALGORITHM=INPLACEALGORITHM=COPY这两个选项中,INPLACE要比COPY性能好,因为INPLACE既不会记录UNDO LOG,也不写REDO LOG,同时执行期间提供DML操作。
Online DDL对不同的DDL语句具有不同的执行规则,下面的表格将详细描述各个语法对Online DDL的支持情况。
列说明:
ALGORITHM=INPLACEALGORITHM=INPLACE,那么则是原地修改 INPLACE**(淡黄色标记)**ALGORITHM=INPLACE,那么则是COPY,拷贝到临时文件修改,并且不支持UPDATE DELETE INSERT操作**(深褐色标记)**
针对是否支持INPLACE、是否需要REBUILD及是否仅修改metadata来分类,选取每类一个DDL SQL来测试,见下图:

考虑到varchar变化长度的问题,这里加测多这一项。
测试过程中的截图,不在此描述,直接粘贴测试结果,感兴趣的筒子们,可以自行测试。
VARCHAR按字符存储,每个字符按照字符集来计算字节,UTF8是3个字节一个字符,当VARCHAR的字节数<256byte时,只需要1个byte来存储实际长度,当VARCHAR字节数>=256时,则需要2个byte来存储实际长度。举例,UTF8字符集下的VARCHAR(10),假设存储 N (0<=N<=10),则其占用的字节数为:N3+1;UTF8字符集下的VARCHAR(100),假设存储 N (0<=N<=100),则其占用的字节数为:N3+2。
理解了这一点后,就可以理解 增长或缩短列的长度这列DDL的处理方式,假设列 VARCHAR(M)需要增大或缩小到VARCHAR(N),字符集是UTF8:

在Online DDL之前,都会习惯性的把同个表格的所有DDL语句合并为一个SQL语句,避免重复Rebuild、多次加锁导致不提供DML时长增加等弊端。
但是,引入Online DDL后,需要有2点改观:

举个例子,现在上线项目,需要对表格tbddl,1个字段由INT修改为VARCHAR,新增3个字段,2个索引,2个默认值,2个列增长长度,单独的SQL 为:
alter table tbddl alter column ItemId varchar(20);
ALTER TABLE tbddl ADD su int;
ALTER TABLE tbddl ADD xin varchar(40);
ALTER TABLE tbddl ADD yu int;
CREATE INDEX IX_SU ON tbddl(SU);
CREATE INDEX IX_yu ON tbddl(yu);
ALTER TABLE tbddl ALTER COLUMN CreatedById SET DEFAULT 123456;
ALTER TABLE tbddl ALTER COLUMN ItemID SET DEFAULT 654321;
ALTER TABLE tbddl ALTER COLUMN CreatedByName VARCHAR(70);
ALTER TABLE tbddl ALTER COLUMN ModifiedByName VARCHAR(100);
测试建议以下执行方式:
alter table tbddl alter column ItemId varchar(20);
ALTER TABLE tbddl ADD su int ,ADD xin varchar(40) ,ADD yu int,ALTER COLUMN ModifiedByName VARCHAR(100),add index ix_su(SU), add index ix_yu(yu);
ALTER TABLE tbddl ALTER COLUMN CreatedById SET DEFAULT 123456,ALTER COLUMN ItemID SET DEFAULT 654321,ALTER COLUMN CreatedByName VARCHAR(70);DDL期间,假设该SQL执行的时间需要10h,除去waitting metadata lock的时间,rebuild或者inplace的时间需要5小时,那么在从库是单线程SQL THREAD应用relay log的情况,需要考虑从库滞后的影响。
DDL在主库执行情况,由于DDL语句没有提交,所以不会同步到从库上,从库可以正常同步其他数据修改操作,这个环节没有问题,但是当DDL在主库提交后,该binlog日志通过IO_THREAD传送到从库的RELAY LOG上,从库的SQL_Thread是单线程工作,应用RELAY log的时候,至少需要5个小时,也就是这5个小时都用来执行RELAY LOG,无法同步主库几个小时内产生的BINLOG,那么,从库就会发生严重的滞后情况,这个问题是否在可接受范围内,需要纳入到DDL执行造成的影响范围内。
如果不能接受从库这么大的滞后,有什么法子可以处理呢?
(注意这里指的是已经落后了,怎么开始追,DDL期间是一定落后的,并行复制也会)
可以通过这个思路来,从库启动并行复制。启动并行复制,需要注意这几个问题:
单实例主上操作
sysbench oltp_common --mysql-host=172.17.0.3 --mysql-user=root --mysql-port=3306 --mysql-password=root --mysql-db=server_234_db2 --db-driver=mysql --tables=8 --table-size=100000 --report-interval=1 --threads=8 preparesysbench oltp_read_write --mysql-host=172.17.0.3 --mysql-user=root --mysql-port=3306 --mysql-password=root --mysql-db=server_234_db2 --db-driver=mysql --tables=8 --table-size=100000 --report-interval=1 --threads=8 --time=600 run
gh-ost --host=172.17.0.3 --user=root --password=root --database=server_234_db2 --table=sbtest5 --alter="add column t1 int not null default 0,add column t2 int not null default 0" --allow-on-master --execute主从模式
有2个选择,一是按照1直接在主上执行同步到从上,另一个连接到从库,在主库做迁移(只要保证从库的binlog为ROW即可,主库不需要保证):
gh-ost --user="root" --password="root" --host=192.168.163.130 --database="test" --table="t" --initially-drop-old-table --alter="ADD COLUMN y1 varchar(10),add column y2 int not null default 0 comment 'test' " --execute此时的操作大致是:
在执行DDL中,从库会执行一次stop/start slave,要是确定从的binlog是ROW的话可以添加参数:–assume-rbr。如果从库的binlog不是ROW,可以用参数–switch-to-rbr来转换成ROW,此时需要注意的是执行完毕之后,binlog模式不会被转换成原来的值。–assume-rbr和–switch-to-rbr参数不能一起使用。
① 检查有没有外键和触发器。 ② 检查表的主键信息。 ③ 检查是否主库或从库,是否开启log_slave_updates,以及binlog信息 ④ 检查gho和del结尾的临时表是否存在 ⑤ 创建ghc结尾的表,存数据迁移的信息,以及binlog信息等 —以上校验阶段 ⑥ 初始化stream的连接,添加binlog的监听 —以下迁移阶段 ⑥ 创建gho结尾的临时表,执行DDL在gho结尾的临时表上 ⑦ 开启事务,按照主键id把源表数据写入到gho结尾的表上,再提交,以及binlog apply。 —以下cut-over阶段 ⑧ lock源表,rename 表:rename 源表 to 源_del表,gho表 to 源表。 ⑨ 清理ghc表。

gh-ost 放弃了触发器,使用 binlog 来同步。gh-ost 作为一个伪装的备库,可以从主库/备库上拉取 binlog,过滤之后重新应用到主库上去,相当于主库上的增量操作通过 binlog 又应用回主库本身,不过是应用在幽灵表上。
**gh-ost 首先连接到主库上,根据 alter 语句创建幽灵表,然后作为一个”备库“连接到其中一个真正的备库上,一边在主库上拷贝已有的数据到幽灵表,一边从备库上拉取增量数据的 binlog,然后不断的把 binlog 应用回主库。**图中 cut-over 是最后一步,锁住主库的源表,等待 binlog 应用完毕,然后替换 gh-ost 表为源表。gh-ost 在执行中,会在原本的 binlog event 里面增加以下 hint 和心跳包,用来控制整个流程的进度,检测状态等。这种架构带来诸多好处,例如:
https://www.cnblogs.com/xinysu/p/6732646.html https://dev.mysql.com/doc/refman/5.7/en/innodb-online-ddl.html https://www.cnblogs.com/waynechou/p/ptosc_onlineddl.html https://www.cnblogs.com/zhoujinyi/p/9187421.html http://mysql.taobao.org/monthly/2018/05/02/