标签 数据库运维 下的文章

关于 gh-ost

gh-ost 是 GitHub 开发的一个 MySQL 在线表结构变更工具(online schema migration tool)。它的全称是 "GitHub's Online Schema Translator"。

gh-ost 现在已经是大型互联网公司进行数据库运维的重要工具。

主要作用

gh-ost 允许在不锁表、不影响业务的情况下,对 MySQL 数据库表进行结构变更(如添加列、修改索引等)。

核心特点

  1. 无触发器设计 - 不像传统工具使用触发器来同步数据,gh-ost 通过解析 binlog 来捕获变更
  2. 可暂停/恢复 - 可以随时暂停迁移过程,对生产环境更友好
  3. 可测试 - 支持在从库上测试变更,确认无误后再应用到主库
  4. 动态调整 - 可以实时调整迁移速度,避免影响线上服务

工作原理

  1. 创建一个与原表结构相同的"影子表"(ghost table)
  2. 在影子表上执行 DDL 变更
  3. 通过 binlog 将原表的增量数据同步到影子表
  4. 数据同步完成后,快速切换表名完成迁移

使用方法

  1. 安装
    gh-ost 可以直接从最新的 发布页面 下载二进制文件,支持 Linux 和 macOS。
  2. 基本命令

    • 测试迁移

      gh-ost --test-on-replica --database=mydb --table=mytable --alter="ADD COLUMN new_col INT" --execute
    • 真实迁移

      gh-ost --database=mydb --table=mytable --alter="ADD COLUMN new_col INT" --execute

实际例子

假设你有一个用户表需要添加新字段:

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 格式不支持。

-- 检查 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 被清理,会导致迁移失败需要重新开始。

实践建议

# 先在从库测试
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

聊聊复制过滤的那些隐藏陷阱

适合读者:DBA / 后端架构师 / 运维工程师

关键词:MySQL 复制、binlog_do_db、replicate_do_db、数据不一致

一、背景

在许多 MySQL 体系的数据库环境中,为了降低 binlog / relay log 日志量、缓解从库复制压力或减少同步延迟,往往会引入 主库 binlog 过滤从库复制过滤 的配置方案。 这些手段在一定程度上能够缓解资源消耗,但如果对其工作机制理解不充分,使用了不合理的过滤策略,极易引入隐蔽且不可逆的数据不一致风险。更为危险的是,这类问题在系统运行过程中通常不会立刻暴露,当业务侧发现数据异常时,往往已经无法通过常规手段进行补救。

本文将从 主库与从库两种过滤方式的实现机制入手,分析它们各自的优缺点及潜在风险。

二、复制过滤的判断逻辑

明确主库和从库在处理 SQL 和 row event 时的判断逻辑存在差异。

2.1 主库:是否写 binlog

判断发生在 SQL 执行完成之后。

binlog_do_db / binlog_ignore_db 仅根据当前会话的 USE db 判断,而不关注 SQL 实际操作的目标表。

2.2 从库:是否执行 relay log

判断发生在 SQL Thread 回放阶段

判断依据包括:

  1. replicate_* 复制过滤参数
  2. row event 真实的 db / table
  3. 表是否存在
  4. GTID 执行状态

结论:当主库和从库判断条件不一致时,即使 binlog 已记录,从库也可能未执行对应 row event,从而导致数据不一致。

三、主库过滤参数及风险

3.1 binlog_do_db / binlog_ignore_db 的行为示例

主库参数设置:
binlog_do_db = db1

主库执行SQL:
USE db1;
INSERT INTO db2.t2 VALUES (1);

执行结果:

  1. 主库 binlog 会记录该事务。
  2. 记录的 row event 目标表为 db2.t2,与 USE db1 不一致。

3.2 相关风险

  1. binlog 语义与实际操作对象脱钩
  2. 新从库或延迟从库无法补全缺失数据
  3. binlog 回放、审计等可能出现语义错误

四、从库复制过滤参数及风险

4.1 常用复制过滤参数

从库复制过滤前提条件就是主库的binlog必须完整。

  1. Replicate_Do_DB:
  2. Replicate_Ignore_DB:
  3. Replicate_Do_Table:
  4. Replicate_Ignore_Table:
  5. Replicate_Wild_Do_Table:
  6. Replicate_Wild_Ignore_Table:

4.2 复制或忽略库参数

说明:

Replicate_Do_DB/Replicate_Ignore_DB 这两个参数一个是只同步某些库,另一个是只忽略某些库,判断依据是relay log中记录use的数据库,并不是SQL语句实际操作的库。

测试:

  1. 从库配置复制过滤
STOP SLAVE;
CHANGE REPLICATION FILTER Replicate_Do_DB = test1;
START SLAVE;
  1. 主库不配置过滤并执行操作
USE test;
CREATE TABLE TEST1.T1 LIKE TEST.T1;
INSERT INTO TEST1.T1 VALUES(1,'A');
  1. 验证数据

主库查看数据:

greatsql> SELECT * FROM TEST1.T1;
+----+-------+
| id | cname |
+----+-------+
|  1 | A     |
+----+-------+
1 row in set (0.00 sec)

从库查看数据:

greatsql> SELECT * FROM TEST1.T1;
ERROR 1146 (42S02): Table 'test1.t1' doesn't exist

结论:

从库报错表不存在,所以这样会导致从库同步数据失败,因为use的是test库。

风险:

多库写入(跨库SQL)、存储过程、触发器、应用层不指定USE库都会导致数据不同步的风险。

4.3 复制或忽略表参数

说明:

Replicate_Do_Table/Replicate_Ignore_Table 这两个参数一个是只同步指定表,另一个是只忽略指定表,两个参数都不支持通配符,可以精确到表但使用要确保库名表名正确。

测试:

  1. 从库配置复制过滤
STOP SLAVE;
CHANGE REPLICATION FILTER Replicate_Ignore_Table= (test1.t1_tmp);
START SLAVE;
  1. 主库不配置过滤并执行DDL操作
RENAME TABLE test1.t1 TO test1.t1_bak;
RENAME TABLE test1.t1_tmp TO test1.t1;
  1. 验证数据

主库查看数据:

greatsql> use test1
Database changed
greatsql> show tables;
+-----------------+
| Tables_in_test1 |
+-----------------+
| t1              |
| t1_bak          |
+-----------------+
2 rows in set (0.01 sec)

从库查看数据:

greatsql> USE test1
Database changed
greatsql> SHOW tables;
+-----------------+
| Tables_in_test1 |
+-----------------+
| t1_bak          |
| t1_tmp          |
+-----------------+
2 rows in set (0.01 sec)

结论:

由于主库执行rename操作将t1表更为t1_bak,t1_tmp更为t1,而从库忽略了t1_tmp导致sql同步失败,如果业务往新t1表插入数据从库就会因表不存在而断开复制链路,这是典型的“表级过滤被 DDL 绕过”事故。

风险:

  1. 未匹配的表默认全部不复制
  2. 新增表需要人工维护配置
  3. 与 DDL 操作存在天然冲突
  4. 如果过滤表过多添加在配置文件中只能一个参数匹配一个表

4.4 指定复制或忽略库参数

说明:

Replicate_Wild_Do_Table/Replicate_Wild_Ignore_Table 这两个参数一个是同步指定表,另一个是忽略指定表,两个参数都支持通配符,使用要确保库名表名没有通配符的隐患存在。

匹配方式%_(LIKE 语义)

测试:

  1. 从库配置复制过滤

忽略日志类表,不需要同步到从库。

STOP SLAVE;
CHANGE REPLICATION FILTER Replicate_Wild_Ignore_Table = (test1.log%);
START SLAVE;
  1. 主库不配置过滤并执行DML操作

一年后业务上线新业务test1.log_important

  1. 验证表结构

主库查看数据:

greatsql> USE test1
Database changed
greatsql> SHOW tables;
+-----------------+
| Tables_in_test1 |
+-----------------+
| log_important   |
+-----------------+
1 row in set (0.00 sec)

从库查看数据:

greatsql> USE test1
Database changed
greatsql> SHOW tables;
Empty set (0.00 sec)

结论:

log_importantlog_% 命中新业务数据未同步到从库,主从复制正常但是从库数据丢失,如果主库故障切换到从库才发现数据不一致就会导致故障,这是典型的“通配规则忽略业务表”事故。

风险:

  1. 匹配范围过宽
  2. 新表“自动进入过滤范围”
  3. DDL 影响范围不可控

五、最常见的踩坑配置

主库从库风险是否推荐
binlog_do_dbReplicate_Do_DB/Replicate_Ignore_DB跨库静默丢数据不推荐
binlog_do_dbreplicate_wild_ignore从库失效不推荐
binlog_ignore_db无过滤永久不可补不推荐
无过滤Replicate_Do_DB/Replicate_Ignore_DB跨库静默丢数据不推荐
无过滤Replicate_Do_Table/Replicate_Ignore_Table与DDL操作存在冲突,人工维护成本高可用,前提是过滤表数量少
无过滤Replicate_Wild_Do_Table/Replicate_Wild_Ignore_Table匹配范围过宽,通配符需要转义可用,前提是确保通配符不会影响其他表

六、最终建议(可直接当规范)

  1. 如果可以不做过滤就不做,做了就会有数据风险。
  2. 主库禁止做库表忽略,主库的binlog必须完整。
  3. 从库Replicate_Do_DB/Replicate_Ignore_DB最好不使用,业务操作并非DBA可以控制,但数据不一致就是DBA的锅。
  4. 从库Replicate_Do_Table/Replicate_Ignore_Table看似精确,但对 DDL 极其敏感,一旦表结构或命名发生变化,复制语义就可能在无感知的情况下被破坏。
  5. 从库Replicate_Wild_Do_Table/Replicate_Wild_Ignore_Table可以使用,库表都可做过滤,前提是一定要做转义,规避不应该发生的数据问题。
  6. 有条件可以使用GreatSQL 的gt checksum工具定期做主从数据校验。