0
点赞
收藏
分享

微信扫一扫

Mysql 表结构变更方案对比及分析

简述

在日常Mysql数据库运维工作中,避免不了要对数据库的表结构做变更,一般都会涉及到增加、删除、修改字段等ALTER操作,在大表场景中,特别是千万级别、甚至亿级别的表数据据,如果处理不当,这些操作将引发锁表的隐患,会导致数据无法更新,从而导致服务异常,甚至导致服务不可用,现对Mysql的几种常见表结构变更方案进行对比


方案

Mysql 原生DDL

Mysql 5.5 

在mysql5.6以前,DDL操作主要有copy和inplace方式,inplace(Fast Index Create)不拷贝数据,相对较快,但仅支持索引的添加和删除,copy方式整个过程都会全程锁表,原理如下:

Mysql 表结构变更方案对比及分析_gh-ost

copy方式:
(1)新建带索引的临时表
(2)锁原表,禁止DML,允许查询
(3)将原表数据拷贝到临时表(无排序,一行一行拷贝)
(4)进行rename,升级字典锁,禁止读写
(5)完成创建索引操作

inplace方式:
(1)新建索引的数据字典
(2)锁表,禁止DML,允许查询
(3)读取聚集索引,构造新的索引项,排序并插入新索引
(4)等待打开当前表的所有只读事务提交
(5)创建索引结束

Mysql 5.6

从5.6.7版本后,才有了正真的ONLIEN DDL,同样包含copy和inplace方式,而对于inplace方式,又可以细分为rebuild方式和no-rebuild方式,rebuild方式指需要重新组织记录的操作如添加删除列或交换列顺序等操作,而no-rebuild方式指不会导致记录格式发生变化的操作如删除和添加索引,引入了row_log来记录DDL期间写操作所产生的日志,因此除DDL操作开始和结束的两小段时间需要对表持EXCLUSIVE-MDL锁禁止读写外,其余DDL操作阶段允许其他会话对表进行读写。

Prepare阶段:

创建新的临时frm文件
持有EXCLUSIVE-MDL锁,禁止读写
根据alter类型,确定执行方式(copy,online-rebuild,online-norebuild)
更新数据字典的内存对象
分配row_log对象记录增量
生成新的临时ibd文件
ddl执行阶段:

降级EXCLUSIVE-MDL锁,允许读写
扫描old_table的聚集索引每一条记录rec
遍历新表的聚集索引和二级索引,逐一处理
根据rec构造对应的索引项
将构造索引项插入sort_buffer块
将sort_buffer块插入新的索引
处理ddl执行过程中产生的增量(仅rebuild类型需要)
commit阶段

升级到EXCLUSIVE-MDL锁,禁止读写
重做最后row_log中最后一部分增量
更新innodb的数据字典表
提交事务(刷事务的redo日志)
修改统计信息
rename临时idb文件,frm文件
变更完成




Mysql 5.7

支持了对于tinyint、int、smallint、bigint等数值类型的数据类型,自身位大小的增大或减小是支持ONLINE,增加了ALTER TABLE RENAME INDEX的语法支持,同时支持ONLINE DDL特性 ,增加了VARCHAR列的长度操作支持ONLINE DDL 特性。原理如下:

Mysql 表结构变更方案对比及分析_pt-osc_02

ALTER TABLE sbdb.test ADD COLUMN _new_columnALGORITHM =inplace,LOCK = default;
ALGORITHM子句用来指定执行DDL所采用的方式,取值为{DEFAULT|INPLACE|COPY}
•ALGORITHM = COPY
•ALGORITHM = INPLACE
•ALGORITHM = DEFAULT

LOCK子句描述持有的锁的类型来控制DML的并发,取值{DEFAULT|NONE|SHARED|EXCLUSIVE}
•LOCK = EXCLUSIVE
•LOCK = SHARED
•LOCK = NONE
•LOCK = DEFAULT

Mysql 8.0

对 DDL 的实现重新进行了设计,其中一个最大的改进是 DDL 操作支持了原子特性。另外,Online DDL 的 ALGORITHM 参数增加了一个新的选项:INSTANT,只需修改数据字典中的元数据,无需拷贝数据也无需重建表,同样也无需加排他 MDL 锁,原表数据也不受影响。整个 DDL 过程几乎是瞬间完成的,也不会阻塞 DML,大致原理如下。

持有MDL_SHARED_UPGRADABLE锁,检测表时是否存在
升级到MDL_EXCLUSIVE锁,禁止读写
更新数据字典对象
分配row_log对象记录增量
生成新表
降级为MDL_SHARED_UPGRADABLE,允许对原表进行读写(wait_while_table_is_used)
用DDL事务的上下文,扫描老表的中,对该事务可见的数据,并用merge排序,最后插入到新表,详细见row_merge_build_indexes。PS:由于使用到merge外排序,所以会受到innodb_sort_buffer_size的限制。
在执行期间,原表的读写不阻塞,增量应用到原表中,并且会记录到row_log中
进入commit阶段,升级到MDL_EXCLUSIVE锁,禁止读写
在新表中apply row_log里的增量(row_log_apply)
更新innodb的数据字典表
提交DDL事务
重命名新表的ibd文件

各版本支持

本文数据全部来自 MySQL 官方文档,此处进行一个集中的整理和总结:

操作

版本

INSTANT

INPLACE

重建表

并发 DML

仅修改元数据

二级索引

创建二级索引

MySQL 8.0

No

Yes

No

Yes

No

MySQL 5.7

Yes

No

Yes

No

MySQL 5.6

Yes

No

Yes

No

删除索引

MySQL 8.0

No

Yes

No

Yes

Yes

MySQL 5.7

Yes

No

Yes

Yes

MySQL 5.6

Yes

No

Yes

Yes

重命名索引

MySQL 8.0

No

Yes

No

Yes

Yes

MySQL 5.7

Yes

No

Yes

Yes

MySQL 5.6

增加全文索引

MySQL 8.0

No

Yes*

No*

No

No

MySQL 5.7

Yes*

No*

No

No

MySQL 5.6

Yes*

No*

No

No

增加空间索引

MySQL 8.0

No

Yes

No

No

No

MySQL 5.7

Yes

No

No

No

MySQL 5.6

修改索引类型

MySQL 8.0

Yes

Yes

No

Yes

Yes

MySQL 5.7

Yes

No

Yes

Yes

MySQL 5.6

Yes

No

Yes

Yes

主键

增加主键

MySQL 8.0

No

Yes*

Yes*

Yes

No

MySQL 5.7

Yes*

Yes*

Yes

No

MySQL 5.6

Yes*

Yes*

Yes

No

删除主键

MySQL 8.0

No

No

Yes

No

No

MySQL 5.7

No

Yes

No

No

MySQL 5.6

No

Yes

No

No

重建主键

MySQL 8.0

No

Yes

Yes

Yes

No

MySQL 5.7

Yes

Yes

Yes

No

MySQL 5.6

Yes

Yes

Yes

No

列操作

新增列

MySQL 8.0

Yes*

Yes

No*

Yes*

No

MySQL 5.7

Yes

Yes

Yes*

No

MySQL 5.6

Yes

Yes

Yes*

No

删除列

MySQL 8.0

No

Yes

Yes

Yes

No

MySQL 5.7

Yes

Yes

Yes

No

MySQL 5.6

Yes

Yes

Yes

No

重命名列

MySQL 8.0

No

Yes

No

Yes*

Yes

MySQL 5.7

Yes

No

Yes*

Yes

MySQL 5.6

Yes

No

Yes*

Yes

调整列顺序

MySQL 8.0

No

Yes

Yes

Yes

No

MySQL 5.7

Yes

Yes

Yes

No

MySQL 5.6

Yes

Yes

Yes

No

修改列默认值

MySQL 8.0

Yes

Yes

No

Yes

Yes

MySQL 5.7

Yes

No

Yes

Yes

MySQL 5.6

Yes

No

Yes

Yes

修改列数据类型

MySQL 8.0

No

No

Yes

No

No

MySQL 5.7

No

Yes

No

No

MySQL 5.6

No

Yes

No

No

扩展 VARCHAR 长度

MySQL 8.0

No

Yes

No

Yes

Yes

MySQL 5.7

Yes

No

Yes

Yes

MySQL 5.6

删除列默认值

MySQL 8.0

Yes

Yes

No

Yes

Yes

MySQL 5.7

Yes

No

Yes

Yes

MySQL 5.6

Yes

No

Yes

Yes

修改自增值

MySQL 8.0

No

Yes

No

Yes

No*

MySQL 5.7

Yes

No

Yes

No*

MySQL 5.6

Yes

No

Yes

No*

修改列为空

MySQL 8.0

No

Yes

Yes*

Yes

No

MySQL 5.7

Yes

Yes*

Yes

No

MySQL 5.6

Yes

Yes*

Yes

No

修改列为非空

MySQL 8.0

No

Yes*

Yes*

Yes

No

MySQL 5.7

Yes*

Yes*

Yes

No

MySQL 5.6

Yes*

Yes*

Yes

No

修改列 ENUM 值

MySQL 8.0

Yes

Yes

No

Yes

Yes

MySQL 5.7

Yes

No

Yes

Yes

MySQL 5.6

Yes

No

Yes

Yes

表操作

修改 ROW_FORMAT

MySQL 8.0

No

Yes

Yes

Yes

No

MySQL 5.7

Yes

Yes

Yes

No

MySQL 5.6

Yes

Yes

Yes

No

修改 KEY_BLOCK_SIZE

MySQL 8.0

No

Yes

Yes

Yes

No

MySQL 5.7

Yes

Yes

Yes

No

MySQL 5.6

Yes

Yes

Yes

No

指定字符集

MySQL 8.0

No

Yes

Yes*

No

No

MySQL 5.7

Yes

Yes*

No

No

MySQL 5.6

Yes

Yes*

No

No

修改字符集

MySQL 8.0

No

No

Yes*

No

No

MySQL 5.7

No

Yes*

No

No

MySQL 5.6

No

Yes

No

No

OPTIMIZE 表

MySQL 8.0

No

Yes*

Yes

Yes

No

MySQL 5.7

Yes*

Yes

Yes

No

MySQL 5.6

Yes*

Yes

Yes

No

重命名表

MySQL 8.0

Yes

Yes

No

Yes

Yes

MySQL 5.7

Yes

No

Yes

Yes

MySQL 5.6

Yes

No

Yes

Yes


总结

虽然5.6以上8.0以下的本提供了Online DDL操作,但Online DDL仍存在以下问题:

  • 主从复制延迟,只有主库上DDL执行成功才会写入到binlog中,而DDL操作在从库上不能并发执行,对于大表操作,仍会引发严重的复制延迟。
  • 主库执行Online DDL时,不能暂停DDL操作。
  • 使用Inplace方式执行的DDL,发生错误或被KILL时,需要一定时间的回滚期,执行时间越长,回滚时间越长。
  • 使用Copy方式执行的DDL,需要记录过程中的undo和redo日志,同时会消耗buffer pool的资源,效率较低,优点是可以快速停止。
  • Online DDL并不是所有时间段的Online,在特定时间段需要加元数据锁。
  • 允许并发DML的DDL,可能会导致Duplicate entry问题。

开源 pt-osc

Percona 开发了一系列管理维护Mysql的工具,集成在 PERCONA Toolkit工具中,有慢查询分析、主从差异对比、主从差异修复及在线表结构修改等工具(pt-online-schema-change )

原理介绍

Mysql 表结构变更方案对比及分析_DDl_03

1. 创建一个与原表结构相同的空表,表名是 _new 后缀;

2. 修改步骤 1 创建的空表的表结构;

3. 在原表上加三个触发器:delete/update/insert,用于 copy 数据过程中,将原表中要执行的语句在新表中执行;

4. 将原表数据以数据块(chunk)的形式 copy 到新表;

5. rename 原表为 old 表,并把新表 rename 为原表名,然后删除旧表;

6. 删除触发器。

总结

1、原表不能有触发器

2、表中至少要有PRIMARY KEY或者unique index,pt-osc工具会创建DELETE TRIGER

3、外键的处理需要对alter-foreign-keys-method参数进行指定,存在风险

4、在pt-osc工具执行的过程中,如果有对主键的更新操作则会出现重复的数据

5、使用过程中注意磁盘空间,会有一倍空间的增长

6、触发器与原始查询共享相同的事务空间。原始查询会在表上有锁竞争,触发器也会在另一张表上有锁竞争。同时,在触发器删除时同样会有元数据锁,详情可见​​作者​​的阐述。

7、触发器无法暂停,当实例负载变高时,考虑到业务的重要性可能会停止变更,但不会停止,所以变更时一定要用独立的账号,不要用业务账号,


开源 gh-ost

gh-ost基于 golang 语言,是 github 开源的一个 DDL 工具,是 GitHub's Online Schema Transmogrifier/Transfigurator/Transformer/Thingy 的缩写,解决了目前采用pt-online-schema-change遇到的一些问题,作者也是openark kit工具集的作者

原理介绍

新建两张表,一张_gho的影子表,gh-ost会将原表数据以及增量数据都应用到这个表,最后会将这个表和原表做次表名切换,另一张是_ghc表,这个表是存放changelog的数据,包括信号标记,心跳等。

gh-ost会开两个goroutine,一个用于拷贝原表数据,一个用于apply增量的binlog到_gho表,并且两个goroutine的并行在跑的,也就是不用关心数据是先拷贝过去还是先apply binlog过去。这里会对insert语句做调整,首先拷贝的insert into会改写成insert ignore into,而binlog内insert into会改写成replace into。

当原表数据全部拷贝完成后,gh-ost会进入到表交换阶段,采用更加安全的原子交换。

Mysql 表结构变更方案对比及分析_DDl_04


总结

1、无触发器

gh-ost没有使用触发器。它通过分析binlog日志的形式来监听表中的数据变更。因此它的工作模式是异步的,只有当原始表的更改被提交后才会将变更同步到临时表,要求binlog是RBR格式(基于行的复制)

2、轻量级

没有使用触发器,对主库的影响比较小的。在操作的过程中不用担心并发和锁的问题。变更操作都是以流的形式顺序的写到binlog文件中,gh-ost只是读取他们并应用到gh-ost表中。

3、可暂停

所有的写操作都是由gh-ost控制的,并且以异步的方式读取binlog,当限速的时候,gh-ost可以暂停向主库写入数据,限速意味着不会在主库进行复制,也不会有行更新。当限速时gh-ost会创建一个内部的跟踪(tracking)表,以最小的系统开销向这个表中写入心跳事件。

4、可动态控制

可以通过unix socket文件或者TCP端口(可配置)的方式来监听请求,操作者可以在命令运行后更改相应的参数

5、可审计

通过网络接口可以获取gh-ost的状态,参数,服务器等信息。

6、可测试

內建支持测试功能,通过使用--test-on-replica的参数来指定:它可以在从库上进行变更操作,在操作结束时gh-ost将会停止复制,交换表,反向交换表,保留2个表并保持同步,停止复制。可以在空闲时候测试和比较两个表的数据情况


使用建议

在Mysql 8.0以前的版本,建议可以采用如下方法进行工具的选择

Mysql 表结构变更方案对比及分析_触发器_05

参考

​​http://mysql.taobao.org/monthly/2020/05/05/​​

​​https://github.com/github/gh-ost/blob/master/doc/triggerless-design.md​​


举报

相关推荐

0 条评论