⭐ 推荐:社区规则条款 V1.0

千万级大表做在线 DDL 变更,怎样才不影响业务?

GJ
GJ 资深会员 👑黑卡会员
发布于 2026-10-28 01:15 ·6 浏览 ·0 回复
内容摘要

千万级大表在线DDL应先确认属于INSTANT、INPLACE还是COPY,显式指定ALGORITHM和LOCK,在预演环境量耗时与磁盘并调大在线日志缓冲区,再按类型优先原生、必要时选pt-osc或gh-ost,并配好监控限速和回滚预案。

学完这篇,你能拿到一套可直接照做的流程:把千万级大表的在线 DDL 从“赌运气”变成有预演、有工具选型、有监控限速、有回滚预案的固定动作。

第一步:先判断这条 DDL 到底能不能在线做

不是所有 DDL 都能“不影响业务”。MySQL 8.0 的 Online DDL 分三档:INSTANT(只改元数据,秒级)、INPLACE(重建表但允许并发读写)、COPY(锁表复制整表,千万级绝对不能直接上线)。

你要做的第一件事,是查官方手册的“Online DDL Operations”表格,确认这条语句属于哪一档。几个常见结论:

  • 末尾加列:MySQL 8.0.12 起支持 ALGORITHM=INSTANT,秒级完成;8.0.29 起任意位置加列也支持 INSTANT。
  • 加二级索引、删索引:INPLACE + 允许并发 DML。
  • 改列类型(如 varchar(64)→varchar(128)):8.0 里扩大长度可用 INPLACE,但 varchar 缩小、int 改 bigint 之外的多数类型变更会退化成 COPY。
  • 加外键、改字符集:多半是 COPY,慎用。

确认方式是在预演库用同结构小表试跑:语句末尾带上 ALGORITHM=INPLACE, LOCK=NONE,如果不支持,MySQL 会直接报错而不是默默锁表。生产上永远显式写这两个子句,别让优化器替你选。

ALTER TABLE orders ADD COLUMN remark varchar(64) NOT NULL DEFAULT '',
  ALGORITHM=INPLACE, LOCK=NONE;

注意:走 INPLACE 时,变更期间产生的并发 DML 会写进 innodb_online_alter_log_max_size 的缓冲区(默认 128MB),写满就直接报错回滚,几小时的活儿白干。大表变更前把它调到 1G 以上。

第二步:在预演环境量出真实耗时和磁盘占用

拿一份生产数据量级的备份恢复到预演库(数据量差一个数量级,耗时估算就完全没参考价值),在业务低峰期跑一次完整 DDL,同时记录三个数:总耗时、高峰期 Threads_running、临时文件峰值大小。

临时表空间要留够:gh-ost 和 pt-osc 都会建一张幽灵表,额外占用约等于“原表数据 + 索引”的磁盘;原生 INPLACE 用 innodb_tmpdir 指定的临时目录。磁盘打满会让 DDL 失败并可能拖垮实例,这一步别省。

第三步:按变更类型选工具

  • INSTANT 能搞定的:直接用原生语句,不要引入任何工具。
  • 原生 INPLACE 可做的:优先原生,链路短、无额外组件。前提是你能接受变更期间主库额外的 CPU/IO 开销。
  • 必须 COPY 或想彻底解耦的:上 pt-online-schema-change(Percona Toolkit 3.5.x)或 gh-ost(1.1.x)。两者区别是:pt-osc 靠触发器同步增量,要求表有主键或唯一键,存在外键的表基本不可用;gh-ost 靠解析 binlog,无触发器、对业务侵入更小,但要求 binlog_format=ROW、账号有 REPLICATION SLAVE 权限。

gh-ost 典型命令(先 --dry-run 跑一遍,再改 --execute):

gh-ost --host=10.0.0.11 --port=3306 --user=ghost --password=*** \
  --database=shop --table=orders \
  --alter="ADD INDEX idx_created (created_at)" \
  --chunk-size=1000 \
  --max-load='Threads_running=30' \
  --critical-load='Threads_running=60' \
  --max-lag-millis=1500 \
  --allow-on-master --initially-drop-ghost-table \
  --execute

注意:--max-load 是触发后暂停、负载降下来自动续跑;--critical-load 是直接中止退出。两个都必须配,光配一个等于没限速。

第四步:盯住主从延迟和长事务

DDL 本身不可怕,可怕的是把从库拖出延迟、把主库的连接池打满。变更期间盯三个指标:主库 Threads_running、从库 SHOW REPLICA STATUS 里的 Seconds_Behind_Source(8.0.22 之前是 Seconds_Behind_Master)、以及是否有长事务阻塞元数据锁。

开跑前先清一遍长事务:

SELECT trx_id, trx_started, trx_mysql_thread_id, trx_query
FROM information_schema.innodb_trx
WHERE TIMESTAMPDIFF(SECOND, trx_started, NOW()) > 60;

有超过 60 秒未提交的事务就联系对应业务停掉,否则 DDL 拿不到 MDL 会一直排队,后面的所有查询跟着排队,几秒钟就能把整个实例堵死。

注意:会话里设置 SET SESSION lock_wait_timeout = 5;,让 DDL 抢不到锁时快速失败,而不是无限等待。

第五步:准备切换和回滚预案

工具类方案的“切换”(cut-over)是唯一真正锁表的瞬间,gh-ost 通常在 1 秒内完成,但也必须放在低峰期执行,并提前通知业务方这 5 分钟内不要跑大批量写。

回滚预案至少准备两条:一是变更前的表结构 DDL 反向语句,二是数据层面的应急方案(比如变更前 24 小时内的全量备份已确认可恢复)。没有回滚方案的变更不要提交上线审批。

小结

  • 先查 Online DDL 支持矩阵,能 INSTANT 就别上工具,能用原生 INPLACE 就别引第三方组件。
  • 语句永远显式带 ALGORITHM=INPLACE, LOCK=NONE,让不支持的情况直接报错。
  • 大表变更前把 innodb_online_alter_log_max_size 调到 1G 以上,磁盘按“原表 + 索引”的两倍预留。
  • 必须 COPY 时用 gh-ost 或 pt-osc,--max-load 和 --critical-load 同时配置才叫限速。
  • 开跑前清长事务、设 lock_wait_timeout,过程中盯 Threads_running 和从库延迟。
  • 切换窗口放低峰期,回滚语句和备份恢复路径提前验证过。
本文转载自 GJ站长论坛,原文地址:https://www.gj0.com/thread-2642.html
转载请注明出处,版权归原作者所有。
—/ 5暂无评分
还没有评分,来打第一分吧

全部回复 0

还没有回复,来抢沙发~