千万级大表做在线 DDL 变更,怎样才不影响业务?
千万级大表在线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和从库延迟。 - 切换窗口放低峰期,回滚语句和备份恢复路径提前验证过。
转载请注明出处,版权归原作者所有。