关于 gh-ost
gh-ost 是 GitHub 开发的一个 MySQL 在线表结构变更工具(online schema migration tool)。它的全称是 "GitHub's Online Schema Translator"。
gh-ost 现在已经是大型互联网公司进行数据库运维的重要工具。
主要作用
gh-ost 允许在不锁表、不影响业务的情况下,对 MySQL 数据库表进行结构变更(如添加列、修改索引等)。
核心特点
- 无触发器设计 - 不像传统工具使用触发器来同步数据,gh-ost 通过解析 binlog 来捕获变更
- 可暂停/恢复 - 可以随时暂停迁移过程,对生产环境更友好
- 可测试 - 支持在从库上测试变更,确认无误后再应用到主库
- 动态调整 - 可以实时调整迁移速度,避免影响线上服务
工作原理
- 创建一个与原表结构相同的"影子表"(ghost table)
- 在影子表上执行 DDL 变更
- 通过 binlog 将原表的增量数据同步到影子表
- 数据同步完成后,快速切换表名完成迁移
使用方法
-
安装 :
gh-ost可以直接从最新的 发布页面 下载二进制文件,支持 Linux 和 macOS。 -
基本命令:
-
测试迁移 :
bashgh-ost --test-on-replica --database=mydb --table=mytable --alter="ADD COLUMN new_col INT" --execute -
真实迁移 :
bashgh-ost --database=mydb --table=mytable --alter="ADD COLUMN new_col INT" --execute
-
实际例子
假设你有一个用户表需要添加新字段:
bash
gh-ost \
--host=localhost \
--user=root \
--password=password \
--database=mydb \
--table=users \
--alter="ADD COLUMN age INT DEFAULT 0" \
--execute
场景说明:
- 原表
users有 1000 万条数据 - 使用传统
ALTER TABLE可能需要锁表数小时 - 使用 gh-ost 可以在后台逐步完成变更,期间用户可以正常读写数据
- 最后只需要几秒钟的短暂切换时间即可完成迁移
适用场景
- 大表的结构变更(百万级以上数据)
- 需要保证高可用性的生产环境
- 需要精确控制数据库负载的情况
数据库支持范围
gh-ost 目前只适用于 MySQL(包括 Percona Server 和 MariaDB)。它依赖 MySQL 的 binlog 机制,因此不支持 PostgreSQL、Oracle 等其他数据库。
常见的坑
1. 外键约束问题
gh-ost 不支持有外键的表。如果表有外键关系,迁移会失败。
解决办法: 需要先删除外键,迁移完成后再重新添加
2. binlog 格式要求
必须使用 ROW 格式的 binlog,STATEMENT 或 MIXED 格式不支持。
sql
-- 检查 binlog 格式
SHOW VARIABLES LIKE 'binlog_format';
-- 如果不是 ROW,需要修改配置
SET GLOBAL binlog_format = 'ROW';
3. 主键要求
表必须有主键或唯一索引,否则 gh-ost 无法正常工作。
4. 磁盘空间
会创建影子表,需要额外的磁盘空间(大约是原表的大小)。如果磁盘空间不足,迁移会失败。
5. 复制延迟
如果主从复制本身就有延迟,gh-ost 的迁移会进一步加重延迟。需要监控 --max-lag-millis 参数。
6. 触发器冲突
虽然 gh-ost 本身不用触发器,但如果原表上已有触发器,可能会导致数据不一致。
7. 字符集问题
影子表的字符集需要与原表一致,否则可能出现乱码或数据截断。
8. 长时间迁移中断
如果迁移过程很长(几天),期间 MySQL 重启或 binlog 被清理,会导致迁移失败需要重新开始。
实践建议
bash
# 先在从库测试
gh-ost \
--host=slave-host \
--test-on-replica \
--migrate-on-replica \
--database=mydb \
--table=users \
--alter="ADD COLUMN age INT" \
--execute
# 设置合理的限流参数
gh-ost \
--max-load=Threads_running=25 \
--critical-load=Threads_running=100 \
--chunk-size=1000 \
--throttle-query="SELECT HOUR(NOW()) BETWEEN 2 AND 6" \
--execute
替代方案
如果 gh-ost 不适用,可以考虑:
- pt-online-schema-change (Percona Toolkit)
- 原生 Online DDL (MySQL 5.6+ 支持部分操作)
- 对于其他数据库,PostgreSQL 可以用 pg_repack