MySQL DDL--gh-ost学习
gh-ost工作原理
1、首先新建一张ghost表,结构与源表相同
2、使用alter命令修改ghost表
3.1、模拟从库命令获取主库上该表的binlog(基于全镜像的行模式的binlog包含更改前和更改后的所有数据),并解析成语句到ghost表上执行。
3.2、获取源表的数据范围(如按照主键获取到最大值和最小值),然后将数据拆分为多个批次拷贝插入到ghost表中
4、锁住源表,防止用户修改源表数据
5、将源表重命名,将ghost表改名为源表
6、释放表锁,清理gh-ost工具产生的表。
gh-ost与pt-osc对比
如何获取变更数据: gh-ost依赖于的BINLOG日志获取数据拷贝期间的变更数据,要求日志格式为ROW模式,操作更安全。 pt-osc依赖于触发器来获取更新数据,要求原表上无触发器,对业务侵入性更强。 操作线程异常结束影响: gh-ost进程异常结束后,主库上仅保留临时表,不会影响业务。 pt-osc进行异常结束后,主库上原表还保留有触发器,如没及时清理,会影响操作性能。 工具对业务性能影响: pt-osc使用触发器会影响操作性能,在高并发下影响比较明显。 gh-ost使用BINLOG来获取变更操作,不会影响DML操作的性能。 业务负载对工具影响: 在低负载场景下,pt-osc消耗时间更短(部分场景下是gh-ost的一半时间) 在高负载场景下,pt-osc能缓慢执行并执行完成,而gh-osc可能解析BINLOG速度赶不上BINLOG生成速度,导致永远无法执行完成。 在高负载场景下且innodb_autoinc_lock_mode=1时(默认),pt-osc很可能会触发死锁导致异常,而gh-osc不会导致死锁。
gh-ost工作模式
1、连接主库直接修改
直连主库
主库上创建ghost表
新表(ghost表)上直接alter修改表结构
迁移原表数据到新表
拉取解析binlog事件,应用到新表
cut-over阶段,用新表替换掉原表
2、连接从库间接应用到主库
连接从库
校验完后,在主库创建新表
迁移原表数据到新表
模拟从库的从库,拉取解析增量binlog应用到主库
cut-over阶段,用新表替换掉原表
两者不同的点就在于,通过连接从库来进行变更,对主库的性能影响最小
数据一致性问题
由于使用binlog获得的数据总是新于或者等于从源表拷贝的数据:
1、在应用binlog导出的数据时,将UPDATE和DELETE直接应用ghost表,将INSERT修改为REPLACE INTO再应用到ghost表。
2、在copy源表数据到ghost表时,使用INSERT IGNORE来忽略掉ghost表已存在的记录
3、对于在gh-ost工作期间发生的DELETE操作:
A:如果记录在从源表删除前被复制到ghost表, 则ghost表中记录会在应用binlog导出的DELETE命令时删除。
B:使用记录在从源表复制到ghost表之前被删除,则记录不会被复制到ghost表,应用binlog导出的DELETE命令也不会报错。
跨服务器操作问题
假设有一套主从复制A1-->A2,A1为主库,A2为从库,另有一台服务器B1装有gh-ost,可以在B1上执行对A1上表的修改:
1、对于数据拷贝操作,B1发送查询到A1上先获取最大值和最小值,然后在B1上进行拆分成不同批次,再从B1上发送命令给A1执行小范围数据拷贝
2、对于Binlog解析,先模拟B1到A1的搭建复制,从A1上拉取binlog到B1,在B1上解析成SQL命令,再发送到A1上执行。
对于跨服务器执行gh-ost命令,会导致大量数据在数据库服务器到命令服务器之间传输,需要考虑网络带宽和网络稳定
唯一索引问题
如果通过gh-ost来新增唯一索引,由于REPLACE INTO和INSERT IGNORE会受到ghost表上唯一索引的影响,当在唯一索引上存在数据重复时,会导致数据丢失。
重命名原理
在pt-osc或者online ddl中,最后的rename操作一般是耗时比较短,但如果表结构变更过程中,有大查询进来,那么在rename操作的时候,会触发MDL锁的等待,如果在高峰期,这就是个严重的问题。所以gh-ost是怎么做的呢?
gh-ost利用了MySQL的一个特性,就是原子性的rename请求,在所有被blocked的请求中,优先级永远是最高的。
gh-ost基于此设计了该方案:一个连接对原表加锁,另启一个连接尝试rename操作,此时会被阻塞住,当释放lock的时候,rename会首先被执行,其他被阻塞的请求会继续应用到新表。
所有请求在CUT-OVER阶段直到临时表消失前会被阻塞,如果CUT-OVER阶段成功,被阻赛的请求会在新表上执行,如果CUT-OVER阶段失败,则所有的请求会在原表上执行。
gh-ost使用限制
1、要求BINLOG使用ROW格式 2、不支持对有外键或触发器的表进行修改 3、不支持随实例上仅大小写不同的同名表进行修改。 4、不支持MySQL 5.7版本新增的JSON类型 5、不支持MySQL 5.7版本新增的计算列
gh-ost常用参数
-critical-load --max-load Comma delimited status-name=threshold, same format as --max-load. When status exceeds threshold, app panics and quits 改表完成之后是否删除老表。 -ok-to-drop-table Shall the tool drop the old table at end of operation. DROPping tables can be a long locking operation, which is why I'm not doing it by default. I'm an online tool, yes? 如果执行之前发现old-table,删除还是终止?默认终止。 -initially-drop-old-table Drop a possibly existing OLD table (remains from a previous run?) before beginning operation. Default is to panic and abort if such table exists port选项仅对应的是host吗?其他地方都需要设置全 -port int MySQL port (preferably a replica, not the master) (default 3306) 因为gh-ost没有提供recursion-method=processlist方法,因此需要通过throttle-control-replicas指定所有的需要检查的slave,并且注意上面的port仅仅对应于host选项,因此需要host:port的方式来写全称 -throttle-control-replicas string List of replicas on which to check for lag; comma delimited. Example: myhost1.com:3306,myhost2.com,myhost3.com:3307 拷贝每个chunk之后sleep的时间=nice-ratio*copy-chunk-time,默认值是0,表示不sleep -nice-ratio float force being 'nice', imply sleep time per chunk time; range: [0.0..100.0]. Example values: 0 is aggressive. 1.5: for every ms spend in a rowcopy chunk, spend 1.5ms sleeping immediately after 默认是不删除socket文件的,这样当第二次运行的时候报错,提示socket文件已经存在 -initially-drop-socket-file Should gh-ost forcibly delete an existing socket file. Be careful: this might drop the socket file of a running migration! -exact-rowcount actually count table rows as opposed to estimate them (results in more accurate progress estimation)
gh-ost命令模板
./gh-ost \ --max-load=Threads_running=25 \ --critical-load=Threads_running=64 \ --chunk-size=1000 \ --throttle-control-replicas="test02:3306" \ --max-lag-millis=1500 \ --initially-drop-old-table \ --initially-drop-ghost-table \ --initially-drop-socket-file \ --ok-to-drop-table \ --conf="./my.cnf" \ --host="test02" \ --port=3306 \ --user="admin" \ --password="admin" \ --database="test" \ --table="test" \ --verbose \ --alter="drop index id1" \ --switch-to-rbr \ --allow-master-master \ --cut-over=default \ --default-retries=120 \ --panic-flag-file=/tmp/ghost.panic.flag \ --postpone-cut-over-flag-file=/tmp/ghost.postpone.flag \ --execute
参考
https://rj03hou.github.io/mysql/gh-ost/
http://www.itdks.com/dakalive/detail/701 在线DDL原理对比分析和实践视频
https://max.book118.com/html/2017/1105/139006717.shtm 在线DDL原理对比分析和实践PPT