gh-ost菜鸟教程

前言

gh-ost 是 GitHub 开发的一款用于 MySQL 数据库的在线模式迁移解决方案,它支持无触发器的在线模式迁移。这个工具是可测试的,并提供暂停、动态控制/重新配置、审计以及许多操作特权。gh-ost 旨在改变表迁移的范式,它在迁移过程中对主服务器产生的工作量很小,并且与已迁移表上的现有工作负载分离,从而减少对数据库性能的影响。

原理

主要实现原理,首先建两张表,一张_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,这样可以很好的支持两个goroutine的并行。但这样的调整能适用所有的DDL吗?答案是否定的。最后,当原表数据全部拷贝完成后,gh-ost会进入到表交换阶段,采用更加安全的原子交换。

过程

  1. 检查有没有外键和触发器。
  2. 检查表的主键信息。
  3. 检查是否主库或从库,是否开启log_slave_updates,以及binlog信息
  4. 检查gho和del结尾的临时表是否存在
  5. 创建ghc结尾的表,存数据迁移的信息,以及binlog信息等
    —以上校验阶段
  6. 初始化stream的连接,添加binlog的监听
    —以下迁移阶段
  7. 创建gho结尾的临时表,执行DDL在gho结尾的临时表上
  8. 开启事务,按照主键id把源表数据写入到gho结尾的表上,再提交,以及binlog apply。
    —以下cut-over阶段
  9. lock源表,rename 表:rename 源表 to 源_del表,gho表 to 源表。
  10. 清理ghc表

安装

由于gh-ost是基于go1.5编译的,故安装gh-ost之前需要安装go,安装go(我的yum仓库中有go包,故直接使用yum安装,没有的话需要进行下载tar包源码安装)

验证go是否安装成功

1
go env

接下来安装gh-ost,由于gh-ost的tar包再github中,下载的时候可能由于网络的原因导致下载失败,故我们使用gitee中的项目

https://gitee.com/mirrors/gh-ost

我这里直接下载的zip包,然后上传到服务器

下载到服务器上后进行解压

1
unzip gh-ost-master.zip

解压之后进入gh-ost-master目录,

然后再当前的目录下初始化一个git仓库

1
git init

然后需要在build.sh中指定gh-ost版本

打开build.sh脚本设置你需要的gh-ost版本,然后保存,

1
RELEASE_VERSION=v1.1.7

然后运行build.sh脚本

接下来解压适用于你操作系统的tar包,

1
tar xvzf gh-ost-binary-linux-amd64-20251022142618.tar.gz

最后将gh-ost二进制文件移动到/usr/local/bin下,然后使用gh-ost –version验证gh-ost是否安装成功

1
2
mv gh-ost /usr/local/bin/
gh-ost --version

实战(单节点)

我们创建了一个example_table表,然后使用gh-ost删除这个表中的全文索引ftx_first_name

1
2
3
4
5
6
7
8
9
10
11
12
13
14
CREATE TABLE example_table(
id INT AUTO_INCREMENT,
first_name VARCHAR(50),
last_name VARCHAR(50),
email VARCHAR(100),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
status ENUM('active', 'inactive') DEFAULT 'active',
PRIMARY KEY (id),
INDEX idx_last_name (last_name),
FULLTEXT INDEX ftx_first_name (first_name),
INDEX idx_status (status),
INDEX idx_created_at (created_at),
INDEX idx_first_name_last_name (first_name, last_name)
);

1
2
3
4
5
6
7
8
9
gh-ost \
--host=127.0.0.1 \
--user=root \
--password=123456 \
--database=demo1 \
--table=example_table \
--alter="DROP INDEX ftx_first_name" \
--execute \
--allow-on-master

可以看到example_table中的全文索引ftx_first_name已经删除

通过解析binlog我们可以看到首先gh-ost创建了存放changelog的demo1._example_table_ghc表,然后创建了影子表demo1._example_table_gho

1
2
3
4
5
6
7
8
9
create /* gh-ost */ table `demo1`.`_example_table_ghc` (
id bigint unsigned auto_increment,
last_update timestamp not null DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
hint varchar(64) charset ascii not null,
value varchar(4096) charset ascii not null,
primary key(id),
unique key hint_uidx(hint)
) auto_increment=256 comment='gh-ost changelog'
/*!*/;

随后在影子表demo1._example_table_gho执行了ddl,并设置了AUTO_INCREMENT

binlog中继续往下看,在end_log_pos 18708,又创建了demo1._example_table_del

1
2
3
4
create /* gh-ost */ table `demo1`.`_example_table_del` (
id int auto_increment primary key
) engine=InnoDB comment='ghost-cut-over-sentry'
/*!*/;

在end_log_pos 24355,删除了_example_table_del

在end_log_pos 25086,使用rename了

demo1.example_table 重命名为demo1._example_table_del

demo1._example_table_gho重命名为demo1.example_table

最后删除了 _example_table_ghc_example_table_ghk