暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

MySQL OnLine DDL

数据库笔记 2020-07-22
659

MySQL各个版本,对DDL的处理方式分为三种:

①:Copy Table方式: 这是InnoDB最早支持的方式。顾名思义,通过临时表拷贝的方式实现的。新建一个带有新结构的临时表,将原表数据全部拷贝到临时表,然后Rename,完成创建操作。这个方式过程中,原表是可读的,不可写。但是会消耗一倍的存储空间。
②:Inplace方式:这是原生MySQL 5.5,以及innodb_plugin中提供的方式。所谓Inplace,也就是在原表上直接进行,不会拷贝临时表。相对于Copy Table方式,这比较高效率。原表同样可读的,但是不可写。
③:Online方式:这是MySQL 5.6以上版本中提供的方式,也是今天我们重点说明的方式。无论是Copy Table方式,还是Inplace方式,原表只能允许读取,不可写。对应用有较大的限制,因此MySQL最新版本中,InnoDB支持了所谓的Online方式DDL。与以上两种方式相比,online方式支持部分DDL时不仅可以读,还可以写,对于dba来说,这是一个非常棒的改进。
复制


MySQL5.5之前版本中的DDL


5.5 及之前版本 DDL 实现的方式存在如下问题:

copy data 的过程需要额外消耗1倍的存储空间,并且耗时很长。
copy data 的过程有写锁,无法持续对业务提供正常服务。
在实现ddl的过程中,数据库性能有明显的下降。
复制

虽然在MySQL5.5 版本中增加了 IN-Place Plugin 方式,避免了额外的存储开销,但同样会阻塞DML操作。


MySQL5.6后的的OnLine DDL


OnLine DDL分为两种方式:

ALGORITHM=INPLACE,可以避免重建表带来的IO和CPU消耗,保证ddl期间依然有良好的性能和并发。
ALGORITHM=COPY,需要拷贝原始表,所以不允许并发DML写操作,可读。这种copy方式的效率还是不如 inplace ,因为前者需要记录undo和redo log,而且因为临时占用buffer pool引起短时间内性能受影响。
复制

可以通过LOCK参数控制是否锁表,默认mysql尽可能不去锁表,但是像修改主键这样的昂贵操作不得不选择锁表。

LOCK=NONE,即DDL期间允许并发读写涉及的表,比如为了保证 ALTER TABLE 时不影响用户注册或支付,可以明确指定,好处是如果不幸该 alter语句不支持对该表的继续写入,则会提示失败,而不会直接发到库上执行。
LOCK=SHARED,即DDL期间表上的写操作会被阻塞,但不影响读取。
LOCK=DEFAULT,让mysql自己去判断lock的模式,原则是mysql尽可能不去锁表
LOCK=EXCLUSIVE,即DDL期间该表不可用,堵塞任何读写请求。如果你想alter操作在最短的时间内完成,或者表短时间内不可用能接受,可以手动指定。
复制

OnLine DDL的实现过程

online ddl主要包括3个阶段,prepare阶段,ddl执行阶段,commit阶段,rebuild方式比no-rebuild方式实质多了一个ddl执行阶段,prepare阶段和commit阶段类似。下面将主要介绍ddl执行过程中三个阶段的流程。


不同的DDL操作对应不同的处理方式

并不是5.6出了OnLine DDL后就可以随心所欲的使用DDL了,具体采用inplace还是copy,锁不锁表都得看对应的DDL操作。 参考官方文档的对应表:

https://dev.mysql.com/doc/refman/5.7/en/innodb-create-index-overview.html


OnLine DDL测试



1.增加索引(根据官方文档说明,增加索引使用inplace方式,不会rebuild并且不阻塞DML)

由测试结果可知,当给表sbtest2增加索引同时给sbtest2插入一条重复的数据,立即可以返回结果,确实不会阻塞DML。


2.删除字段(根据官方文档说明,删除字段使用inplace方式,会rebuild但不阻塞DML)

由测试结果可知,当给表sbtest2删除字段pad同时给sbtest2插入一条重复数据,立即可以返回结果,确实不会阻塞DML。


3.修改字段类型(根据官方文档说明,修改字段类型不可以使用inplace方式,会rebuild且阻塞DML)

由测试结果可知,当给表sbtest2的c字段修改字段类型为varchar同时插入一条重复数据,插入操作会等待DDL操作结束后再执行。


OnLine DDL注意事项

磁盘空间

  • rebuild 的时候,datadir空间是否足够

  • 因为会拷贝ibd文件,所以要确保空间足够

  • rebuild 的时候,innodbonlinealter_logmaxsize是否足够

  • rebuild过程中,产生的DML涉及到ROW-LOG变更日志,是否足够存储

  • inplace的时候,考虑tmpdir空间是否足够

ddl对从库延迟的影响是否可以接受

  • 主库online DDL的过程中,由于没有commit,所以其他并发操作可以正常同步到从库

  • 主库commit后,DDL同步到从库

  • 如果从库是单线程执行SQL_THREAD,假设DDL执行过程需要1个小时,那么从库将会滞后1小时+

  • 是否允许从库的滞后,如果不允许,可以通过并行复制来优化处理

row-log会检查重复值或者修改冲突吗?

  • 会根据主键及唯一约束来检查

copy table ,inplace下如何暂停DDL操作

  • show full processlist;

  • kill id; #( DDL SQL的id号)

  • 这里kill完后,仍然可以再次正常执行DDL,不会存在冲突,其创建的临时idb及frm文件会自动删除

copy table ,inplace下宕机

  • 这两种情况下宕机后,没有完成的DDL语句不会继续执行

  • 但是,其生成的frm跟idb临时文件不会被删除,可以手动删除,也可以不手动删除,即使不删除,也不会影响再次执行DDL

  • 但建议mysql服务后,删除无用的临时文件

同个表格多个DDL语句,不要一个个执行

  • 请按照是否支持inplace及是否需要rebuild分类合并执行

更新索引统计信息

  • 执行完 ALTER TABLE 之后,最好 ANALYZE TABLE tb1 去更新索引统计信息


发散问题

1.如何实现数据完整性

使用online ddl后,用户心中一定有一个疑问,一边做ddl,一边做dml,表中的数据不会乱吗?这里面关键部件是rowlog。rowlog记录了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

这个错误是什么鬼?这个表#sql-xxx实质是做ddl产生的临时表,ddl异常退出后(比如进程被kill,或者机器异常掉电等),临时文件没有清理。再次执行时,会创建同名的#sql-xxx临时文件,从而导致报错。这里的xxx与table-id强相关,如果是这样,我们把这个讨厌的#sql-xxx临时文件删掉如何呢?再次重做ddl发现还是报同样的错误。这主要原因是,这个临时表信息在innodb的数据字典有残留,通过查询数据字典视图informationschema.innodbsys_tables,可以发现存在一条#sql-xxx的表记录。 深层次原因:ddl整个过程不是原子的,prepare过程中会新建frm文件,ibd文件,并更新数据字典;然后再进行拷贝全量+重放增量操作;最后再rename frm文件,idb文件,并修改数据字典。由于整个过程涉及到server层和innodb层,并不是一个大事务(每次改数据字典都是单独一个事务),所以执行过程中如果异常终止,就会导致临时表数据字典残留在系统表内。

影响:虽然临时表信息残留在数据字典内,但不影响用户后续操作。

解决方法:由于临时表与table-id强相关,如何改变table-id是我们需要做的,但表又不能被修改,table-id改变不了。这就成了一个悖论,要做ddl,需要改变table-id;要改变table-id,又需要通过ddl操作。查看源码后发现,对于online ddl,临时表名依赖于变更表的table-id(比如#sql-ib79,79就是变更表的table-id),而对于copy类型(非online)的ddl,临时表名则不依赖于table-id(由mysqld进程号+连接会话号产生,比如sql-604d2,604d是mysqld进程号,2是会话号)。因此,我们通过copy类型的ddl,就可以产生表名不一样的临时表了,也就可以完成ddl任务了。比如:alter table testlog add column c88 int, ALGORITHM=copy;

其它:ddl异常结束,会导致重做ddl失败。如果做ddl过程中,kill query,这个时候ddl也会退出,但退出前会做好善后工作,清理数据字典,因此再次做ddl不会存在问题。


文章转载自数据库笔记,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论