MySQL在线改表工具gh-ost源码编译与使用方法

gh-ost 是 github开源的MySQL在线改表工具,使用go语言开发,因为没有使用触发器,采用binlog同步增量数据,性能损耗较小,同时也避免了与业务SQL并发执行可能导致的死锁。本文简单介绍gh-ost源码编译安装与使用方法。

一、gh-ost编译安装

1. 前提条件:
go开发环境版本:1.12及以上
go版本升级,参考文章:Linux源码编译安装高版本go语言开发环境

2. 下载gh-ost源码:
git clone https://github.com/github/gh-ost.git

3. 编译:
cd /data/git/gh-ost
./script/build

编译成功后,生成的 gh-ost 可执行文件位于目录:
/data/git/gh-ost/bin/

二、gh-ost使用方法

gh-ost 
-host=127.0.0.1
-user="user" 
-password="passwd" 
-database=" dbname" 
-table="tablename" 
-alter="add column c1 varchar(100)" 
-allow-on-master 
-ok-to-drop-table 
-initially-drop-ghost-table 
-execute

三、gh-ost常用参数

  • -host,MySQL机器IP,通常是一个从库,指定-allow-on-master参数后,可以指定主库。
  • -port,MySQL端口号
  • -user,MySQL用户名
  • -password,MySQL密码
  • -database,数据库名称
  • -table,表名称
  • -alter,改表的SQL语句,比如 add column age int default 0
  • -allow-on-master,允许连接主库读取binlog
  • -ok-to-drop-table,改完表之后,是否删除旧表
  • -execute,执行改表
  • -chunk-size,拷贝原表数据时,一次拷贝的行数
  • -initially-drop-ghost-table,在改表执行之前,删除之前执行失败留下来的gho表。
  • -initially-drop-old-table,在改表执行之前,删除之前留下来的旧表
  • -max-lag-millis,最大复制延迟,大于该值时,gh-ost暂停执行
  • -cut-over-lock-timeout-seconds,改表的cut-over阶段时,获取锁的超时时间

四、gh-ost原理

  1. gh-ost先连接到主库上,创建临时的ghost表,表名以_gho结尾,根据alter语句修改ghost表。
  2. gh-ost创建新的连接,连接到主库或者某一个备库,默认条件下是连接备库,同时将自己模拟成一个备库,一边在主库上从源表拷贝已有的数据到新表(ghost表),一边从主库(或者某一个备库)上拉取增量数据的binlog,然后不断的把 binlog 日志中关于源表的修改部分解析成SQL,应用到主库的ghost表。
  3. cut-over是最后一步,锁住主库的源表,等待binlog 应用完毕,然后将ghost表和源表进行替换。
4.1 源表数据如何拷贝到ghost表?

gh-ost 使用 insert ignore 语法往新表迁移数据,因为迁移数据与应用binlog并发进行,如果binlog先应用,那么迁移数据insert ignore就会忽略该数据。具体的SQL语句如下所示:

insert /* gh-ost `sysbench`.`sbtest1` */ ignore into `sysbench`.`_sbtest1_gho` (`id`, `k`, `c
`, `pad`, `age`, `age1`, `age2`, `age3`, `age4`, `age5`)
      (select `id`, `k`, `c`, `pad`, `age`, `age1`, `age2`, `age3`, `age4`, `age5` from `sysbench`.`sbtest1` force index (`PRIMARY`)
        where (((`id` > _binary'203685')) and ((`id` < _binary'204685') or ((`id` = _binary'204685')))) lock in share mode
      )
4.2 binlog日志如何解析成回放SQL?

gh-ost使用第三方binlog日志解析库 siddontang/go-mysql 进行binlog日志的解析。

  • insert语句解析,转换为replace into语句。
  • update语句解析为update语句。
  • delete语句解析为delete语句 。

源表往新表迁移数据,业务DML操作源表,解析binlog生成SQL应用到新表,binlog由DML操作产生,这三种操作同时进行,无论谁先谁后,在数据迁移完成时,加锁,等待binlog应用完成,最终保证数据一致。

4.3 cut-over过程

gh-ost cut-over设计的很巧妙,也有一点复杂,主要目的是保证在cut-over阶段,不管发生什么异常情况,都能保证数据的一致性。

下面详细分析,C1...C9,C10等等表示不同的连接。

  1. C1..C9:对 tbl 执行正常的DML: INSERT, UPDATE, DELETE
  2. C10:CREATE TABLE tbl_old (id int primary key) COMMENT='magic-be-here'
  3. C10:LOCK TABLES tbl WRITE, tbl_old WRITE
  4. C11..C19:新来的连接,对tbl的dml,由于C10的LOCK被阻塞
  5. C20:RENAME TABLE tbl TO tbl_old, ghost TO tbl 同样被C10的LOCK阻塞, 但是优先级高于 C11..C19 和 C1..C9 和任何企图在tbl上执行 DML的会话
  6. C21..C29:新来的连接,在 tbl上执行dml,但是被C10的 LOCK 和C20的 RENAME阻塞
  7. C10:检查 C20 的 RENAME 是否仍在等待 (在processlist中检查 RENAME )
  8. C10:DROP TABLE tbl_old,什么都不会发生,tbl 仍然是 locked,其他会话也仍然被阻塞
  9. C10:UNLOCK TABLES
  10. unlock后首先执行C20的RENAME , ghost 与 tbl互换, 然后 C1..C9, C11..C19, C21..C29 都在新的 tbl表上执行

注:

  • 创建 tbl_old 防止过早交换表
  • 一个会话在拥有表的write lock后仍可以执行 drop table,但是不能对该表执行rename操作
  • 被阻塞的 rename 优先级永远高于被阻塞的 insert/update/delete,无论谁先开始的,在table的lock释放后,rename优先执行

cut-over过程中任一阶段失败时会发生什么?

  • C10 在 CREATE 时失败,什么都不会发生
  • C10 在 LOCK 时失败, 表不会被锁,业务正常执行其他语句
  • C10在C20即将 RENAME 时异常断开连接,释放锁, C1..C9, C11..C19 立即在 tbl上执行。C20的 RENAME 立即失败,因为 tbl_old 存在。影响仅为C1..C9, C11..C19的操作被锁了一段时间
  • C10 在 C20的RENAME操作被阻塞后异常断开连接,与上面的情况类似,释放锁, C20的 RENAME 立即失败,因为 tbl_old 存在。影响仅为DML操作被锁了一段时间
  • C20 在 C10 drops table之前异常, C10 执行 DROP, UNLOCK,DML恢复正常
  • C20 在 C10 DROP table 之后 UNLOCK 之前异常,C10 执行UNLOCK,DML恢复正常
  • 如果C10和C20都失败,也没有问题,LOCK,RENAME 操作都被释放,C1..C9, C11..C19, C21..C29 可正常操作

因此,无论在cut-over时发生什么异常,整个cut-over都是原子化的,不会导致数据丢失和不一致。

文章评论

0条评论