欢迎光临

MySQL大表在线DDL变更实战:Online DDL、gh-ost与pt-osc原理与选型指南

在生产环境中对千万级乃至亿级数据量的MySQL大表执行DDL(Data Definition Language)变更,一直是DBA和后端工程师最头疼的难题之一。传统的

1
ALTER TABLE

语句会长时间持有表级元数据锁(MDL),阻塞业务读写,甚至导致连接池耗尽、数据库雪崩。MySQL 8.0虽然在Online DDL方面有了长足进步,但仍有许多场景需要借助

1
gh-ost

1
pt-online-schema-change

(pt-osc)这类第三方工具来完成无锁或低锁变更。本文将从原理出发,结合实战配置,系统讲解三种方案的适用场景、工作机制与选型策略。

MySQL大表在线DDL变更实战

一、为什么大表DDL是个难题

要理解在线DDL的难点,首先需要搞清楚MySQL执行DDL时发生了什么。在MySQL 5.6引入Online DDL之前,绝大多数

1
ALTER TABLE

操作都会采用COPY算法:创建一张临时新表、拷贝全量数据、替换旧表。整个过程表不可写,甚至不可读。

即使到了MySQL 8.0,Online DDL也不是万能的。以下几类操作依然可能引发长时间阻塞或大表拷贝:

  • 修改列类型:例如把
    1
    VARCHAR(50)

    改为

    1
    TEXT

    ,只能走COPY算法

  • 修改列字符集或排序规则:需要重建整张表
  • 添加全文索引:耗时与数据量正相关
  • 修改主键:涉及全表数据重组
  • 大表添加二级索引:虽然INPLACE算法不拷贝数据,但构建索引的排序过程会消耗大量IO和临时空间

更隐蔽的风险是元数据锁(MDL)。DDL语句在执行开始时会请求MDL写锁,如果此时有长事务持有MDL读锁,DDL会被阻塞;而DDL一旦排队等待,后续所有对该表的查询和DML都会被堵在队列里,形成典型的DDL雪崩。这就是为什么在生产环境直接执行

1
ALTER TABLE

极其危险。

二、MySQL 8.0 Online DDL详解

2.1 INPLACE与INSTANT算法

MySQL 8.0引入了革命性的INSTANT算法(8.0.12+),对于在表尾添加列这类操作可以瞬间完成——只修改数据字典中的表定义,不修改数据行,也不重建表。这是真正意义上的瞬态DDL。


1
2
3
4
5
6
7
8
9
10
11
12
-- 8.0.12+ 添加列默认使用INSTANT算法,瞬间完成
ALTER TABLE orders ADD COLUMN remark VARCHAR(200) NULL;

-- 显式指定算法
ALTER TABLE orders
  ADD COLUMN status TINYINT NOT NULL DEFAULT 0,
  ALGORITHM=INSTANT;

-- 添加二级索引使用INPLACE算法(不拷贝数据但需构建索引)
ALTER TABLE orders
  ADD INDEX idx_user_created (user_id, created_at),
  ALGORITHM=INPLACE, LOCK=NONE;

LOCK子句控制并发度:

1
LOCK=NONE

允许DML并发执行;

1
LOCK=SHARED

允许读但不允许写;

1
LOCK=EXCLUSIVE

完全阻塞。推荐在生产DDL中显式声明

1
LOCK=NONE

,如果MySQL发现该操作无法以NONE方式执行,会直接报错而不是默默降级为阻塞模式——这比让数据库自作主张安全得多。

2.2 Online DDL的适用范围

下表总结了MySQL 8.0中常见DDL操作支持的算法,供选型参考:

操作类型 INSTANT INPLACE COPY 是否阻塞DML
表尾添加列 支持 支持 支持 INPLACE/INSTANT不阻塞
删除列 8.0.29+部分 重建 支持 INPLACE不阻塞
重命名列 支持 支持 支持 不阻塞
修改列类型 不支持 不支持 支持 阻塞
添加二级索引 不支持 支持 支持 INPLACE不阻塞
修改主键 不支持 部分 支持 INPLACE部分阻塞
修改列默认值 支持 支持 支持 不阻塞
修改字符集 不支持 不支持 支持 阻塞

从表中可以看出,凡是涉及重建表或重写每行数据的操作,MySQL原生Online DDL都无能为力,这时就需要借助第三方工具。

数据库变更流程示意图

三、gh-ost:GitHub开源的无触发器在线DDL工具

3.1 工作原理

gh-ost(GitHub Online Schema Change Migrator)是GitHub为了解决自身海量数据变更问题而开发的开源工具。与传统的pt-osc不同,gh-ost不依赖触发器,而是通过解析binlog来同步增量数据。这一设计避免了触发器带来的性能损耗和死锁风险。

其核心流程如下:

  1. 创建与原表结构一致的影子表(ghost table)
  2. 从原表拷贝数据到影子表(分批chunk拷贝)
  3. 同时作为binlog客户端,实时应用原表上的DML变更到影子表
  4. 校验数据一致性后,通过原子切换(RENAME TABLE)替换原表与影子表
  5. 清理旧表和临时文件

因为不使用触发器,gh-ost对源库的负载可控性更强——它自身就是一个独立的客户端进程,可以随时暂停、动态调整并发度。

3.2 实战命令示例


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 基础用法:给orders表添加列
gh-ost \
  --host=127.0.0.1 \
  --port=3306 \
  --user=ghost-user \
  --password='xxxx' \
  --database=shop \
  --table=orders \
  --alter='ADD COLUMN channel VARCHAR(32) NOT NULL DEFAULT "web"' \
  --allow-on-master \
  --chunk-size=200 \
  --max-load='Threads_running=100' \
  --critical-load='Threads_running=300' \
  --execute

# 动态限流:运行时通过socket文件下发命令
gh-ost ... --serve-socket-file=/tmp/gh-ost.sock &

echo 'throttle-chunk-size=50' | nc -U /tmp/gh-ost.sock
echo 'unthrottle' | nc -U /tmp/gh-ost.sock

几个关键参数值得重点关注:

  • –max-load:软阈值,达到后gh-ost自动暂停拷贝,等待指标回落
  • –critical-load:硬阈值,达到后立即中止整个迁移并回滚
  • –chunk-size:每批拷贝的行数,越小越平滑但耗时越长
  • –max-lag-millis:在主从架构下控制从库延迟上限
  • –switch-to-rbr:自动将binlog格式切换为ROW(gh-ost强制要求ROW格式)

3.3 gh-ost的优势与限制

gh-ost的优势非常明显:无触发器设计避免了源表写入放大;支持动态暂停与限流;支持断点续传(通过

1
--resume

);提供throttle HTTP接口便于与监控系统联动。同时,它也存在一些限制:

  • 要求binlog格式为ROW,且binlog_row_image=FULL
  • 不支持无主键或唯一键的表
  • 外键支持有限(需使用
    1
    --alter-foreign-keys-method

  • 对带触发器的源表支持不佳

四、pt-online-schema-change:Percona Toolkit经典工具

4.1 工作原理

pt-osc是Percona Toolkit套件中的在线表变更工具,基于触发器实现。其工作流程与gh-ost类似,但增量同步是通过在原表上创建三个触发器(INSERT、UPDATE、DELETE)来完成的。每当原表发生DML,触发器会把对应的变更同步到新表。

触发器方案的历史悠久、兼容性好,但也带来了固有的副作用:触发器会增加原表每次DML的执行成本,在高写入并发场景下可能导致性能下降、锁竞争甚至死锁。此外,一个表上不能同时存在多个pt-osc任务(因为触发器名固定)。

4.2 实战命令示例


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 给orders表添加索引,并自动检测从库延迟
pt-online-schema-change \
  --alter='ADD INDEX idx_paytime (paid_at)' \
  --host=127.0.0.1 \
  --user=pt-user \
  --password='xxxx' \
  D=shop,t=orders \
  --chunk-time=0.5 \
  --max-load='Threads_running=80' \
  --critical-load='Threads_running=200' \
  --max-lag=10 \
  --check-slave-lag='slave-host:3306' \
  --execute

# 只执行dry-run,不真正变更
pt-online-schema-change \
  --alter='MODIFY COLUMN name VARCHAR(128) NOT NULL' \
  D=shop,t=users \
  --dry-run

# 对于有外键的表,必须指定处理策略
pt-online-schema-change \
  --alter='DROP COLUMN legacy_field' \
  --alter-foreign-keys-method=auto \
  D=shop,t=orders \
  --execute
1
--alter-foreign-keys-method

有三种取值:

1
auto

让工具自行决定;

1
rebuild_constraints

在新表上重建外键约束;

1
drop_swap

先删原表再重命名新表(有短暂窗口期,不推荐生产使用)。

工具选型对比

五、三种方案对比与选型矩阵

下表从多个维度对比MySQL原生Online DDL、gh-ost与pt-osc,帮助读者根据实际情况做出选择:

维度 Online DDL(原生) gh-ost pt-osc
实现机制 引擎内置 binlog解析 触发器
对源库侵入 低(INPLACE) 极低 中(触发器开销)
可暂停/限流 不支持 支持 支持
断点续传 不支持 支持 支持
要求binlog ROW
外键支持 原生支持 有限 支持(需指定策略)
多任务并行 支持 支持(不同表) 不支持(触发器冲突)
对大表速度
社区维护 Oracle官方 GitHub活跃 Perona活跃

5.1 推荐选型路径

实际工程中,可以按以下优先级顺序选型:

  1. 优先尝试MySQL原生Online DDL:对8.0+版本,能使用INSTANT或INPLACE+LOCK=NONE完成的操作,直接用原生DDL,最简单也最安全
  2. 大表且原生DDL不可用时首选gh-ost:高写入并发、对延迟敏感、binlog已为ROW格式的主库,gh-ost是最佳选择
  3. 兼容性优先时选pt-osc:旧版本MySQL、binlog为STATEMENT格式、或表上有外键且无法修改binlog格式的场景
  4. 极端大表可考虑组合方案:先在从库用gh-ost变更验证,再通过主从切换完成线上变更

六、生产环境实操注意事项

6.1 变更前的准备

无论使用哪种方案,变更前都应做足功课:

  • 备份:务必先做一份完整备份(XtraBackup或mysqldump),变更失败时可以回滚
  • 磁盘空间评估:gh-ost与pt-osc都需要约等于原表大小的额外空间存放影子表
  • 从库延迟基线:记录当前从库延迟,变更过程中持续监控
  • 变更窗口选择:尽量选业务低峰期,降低触发限流的概率
  • Dry-run验证:pt-osc的
    1
    --dry-run

    和gh-ost的

    1
    --test-on-replica

    可以先在从库演练

6.2 变更中的监控

使用以下SQL实时观察变更进度和数据库负载:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
-- 查看gh-ost进度(通过影子表的行数估算)
SELECT
  table_name, table_rows
FROM information_schema.tables
WHERE table_schema='shop' AND table_name LIKE '%_gho%';

-- 监控当前运行中的DDL线程
SELECT id, user, host, time, state, info
FROM information_schema.processlist
WHERE info LIKE 'ALTER%' OR info LIKE 'gh-ost%';

-- 观察Threads_running(pt-osc与gh-ost限流的核心指标)
SHOW GLOBAL STATUS LIKE 'Threads_running';

-- 监控从库延迟
SHOW SLAVE STATUS\G
-- 关注 Seconds_Behind_Master 字段

建议将

1
Threads_running

1
Seconds_Behind_Master

1
Rows_examined

接入告警系统,一旦超过阈值自动通知值班DBA。gh-ost的throttle接口也可以与Prometheus + Grafana联动,实现更精细的自动限流闭环。

6.3 变更后的校验

变更完成后不要立即开放全部流量,应做几项校验:

  • 行数对比:原表与新表行数应一致(注意
    1
    table_rows

    是估算值,必要时用

    1
    COUNT(*)

    精确比对)

  • 索引检查:
    1
    SHOW INDEX FROM tbl

    确认新索引存在

  • 表结构检查:
    1
    SHOW CREATE TABLE tbl

    确认列定义符合预期

  • 抽样数据比对:随机选取若干行,比对关键字段值
  • 慢查询日志观察:变更后一段时间内关注是否有新增慢查询

七、总结

大表在线DDL是MySQL运维中的高频难题,没有银弹方案。MySQL 8.0的Online DDL在加列、加索引等常见场景下已经足够高效,应作为首选;当遇到需要重建表的操作或对可控性要求更高时,gh-ost凭借无触发器设计和灵活的限流机制成为现代化场景的优选;pt-osc则在兼容性和外键处理上仍有不可替代的位置。理解三者的原理差异,掌握动态限流与监控手段,才能在面对亿级大表变更时胸有成竹、稳如磐石。

最后强调一点:任何大表变更都应先在测试环境或从库演练,并准备好完整的回滚预案。生产无小事,DDL需谨慎。

【本站文章皆为原创,未经允许不得转载】:汤不热吧 » MySQL大表在线DDL变更实战:Online DDL、gh-ost与pt-osc原理与选型指南
分享到: 更多 (0)