外观
从4小时停机到0中断:千万级订单大表Online DDL实战
问题背景:峰值期大表加字段的致命风险
去年电商大促前一周,我们的核心订单表数据量突破1200万行,存储占用超过8G,产品侧临时提出需求:给订单表新增coupon_used字段标记优惠券核销状态,用于后续营销返现的逻辑统计。如果按照常规直接执行ALTER TABLE加字段操作,我们在预发环境压测得出结论:整个变更过程会锁表3小时40分钟,期间所有订单写入请求全部阻塞,完全无法适配大促峰值期的业务要求。 此前团队曾踩过直接执行大表DDL的坑:2022年双12期间直接对800万行订单表加索引,导致主库锁表1小时27分钟,全量订单提交接口报错,直接损失超过30万的交易流水。这次如果再出现同类问题,大促峰值的流量冲击会直接击穿整个交易链路,因此必须找到完全无锁、业务无感知的大表变更方案。
方案对比:3种大表DDL方案优劣实测
我们针对当前MySQL 8.0的环境,对行业内常用的3种大表变更方案做了全量压测对比:
- 原生MySQL Online DDL:虽然支持加字段操作全程不锁读,但执行期间如果存在未提交的大事务,DDL线程会被阻塞,后续所有同表的写入请求都会被DDL线程堵在等待队列里,峰值期很容易触发雪崩效应,实测峰值场景下执行该DDL会导致从库延迟最高超过12s,业务读从库大量超时。
- 手动双触发器同步方案:自己创建影子表,写插入、更新、删除三个触发器同步原表的增量数据,分批迁移历史数据,该方案的问题是代码冗余,触发器逻辑容易遗漏边缘场景,出问题后回滚成本极高,没有配套的进度监控能力。
- pt-online-schema-change工具方案:Percona团队开源的Online DDL工具,核心原理是创建结构和原表一致的影子表,在影子表上执行加字段操作,然后分批把原表数据拷贝到影子表,同时在原表上增删改三个触发器同步增量数据,全量拷贝完成后原子性rename替换原表,全程不会对原表加长写锁,我们压测得出该方案峰值场景下从库延迟最高不超过200ms,完全满足业务要求。
实操步骤与可运行代码示例
1. 前置环境校验
首先确认目标表的结构、数据量、索引情况,避免无主键、无有效索引的场景导致拷贝数据效率极低:
sql
-- 查询目标订单表基础信息
SELECT COUNT(*) AS total_rows,
ROUND(DATA_LENGTH/1024/1024, 2) AS table_size_mb
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = 'trade_db' AND TABLE_NAME = 'orders';
-- 查看表索引,pt工具拷贝数据依赖主键做分片遍历
SHOW INDEX FROM orders;
-- 目标订单表简化结构参考
CREATE TABLE `orders` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '订单ID',
`user_id` bigint unsigned NOT NULL COMMENT '用户ID',
`order_no` varchar(32) NOT NULL COMMENT '订单号',
`amount` decimal(10,2) NOT NULL COMMENT '订单金额',
`create_time` datetime NOT NULL COMMENT '创建时间',
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_create_time` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单主表';同时提前确认数据库版本、pt工具版本,本次使用的是pt-online-schema-change 3.3.0版本,适配MySQL 8.0的认证协议。
2. pt-online-schema-change执行命令
执行命令前先设置核心参数:单批次拷贝行数chunk-size设为1000,从库最大允许延迟max-lag设为1s,超过该阈值自动暂停拷贝,避免拖垮从库,同时开启外键自动适配参数:
bash
pt-online-schema-change
--host=127.0.0.1 --port=3306 --user=admin --password=xxx
--alter="ADD COLUMN coupon_used TINYINT NOT NULL DEFAULT 0 COMMENT '优惠券是否已使用 0否1是'"
D=trade_db,t=orders
--chunk-size=1000
--max-lag=1
--check-interval=1
--alter-foreign-keys-method=auto
--progress=time,30
--execute执行过程中工具会每30s输出一次进度日志,我们可以通过日志实时查看拷贝进度,全程不需要人工介入。
3. 变更后数据一致性校验
变更完成后,不要直接切业务流量,先校验数据完整性:
sql
-- 校验新增字段非空数据量是否和预期一致
SELECT COUNT(*) FROM orders WHERE coupon_used = 0;
-- 抽取1000条历史订单做抽样校验,确认没有丢数据、错数据
SELECT id,order_no,coupon_used FROM orders ORDER BY id DESC LIMIT 1000;本次1200万行数据的变更全程耗时17分42秒,峰值期从库延迟最高187ms,没有触发任何业务告警,完全符合预期。
落地避坑总结:3个高频踩坑点修正方案
我们在多次大表Online DDL落地过程中,总结了3个非常容易踩坑的实战注意事项:
- 必须配置外键关联处理参数,避免变更直接报错:很多开发者执行pt变更时遗漏
--alter-foreign-keys-method=auto参数,如果目标表存在关联的外键子表,工具默认会直接终止执行,报错现象为「Cannot drop table because other objects depend on it」,修正方案是变更前提前查询关联外键列表,配置该参数后工具会自动调整影子表的外键关联关系,rename后自动替换为合法的父表引用。 - 高峰期禁止把chunk-size设置过大:不少开发者为了提升变更速度,把单批次拷贝行数设置为10000甚至更高,会导致单次事务锁定行数过多,从库同步延迟超过2s,业务侧读从库的大量请求超时,修正方案是峰值场景下chunk-size设置为500~1000,非峰值场景也不要超过5000,同时开启
--check-slave-lag参数实时检测从库延迟。 - 绝对不能跳过预演和备份步骤直接跑生产:很多团队为了省时间,没有在从库做模拟变更就直接执行生产DDL,容易出现未知异常导致业务受损,正确操作流程是先在从库全量同步生产数据,做一次完整的模拟变更,统计耗时、确认性能影响,变更前用mysqldump对目标表做全量备份,一旦出现异常可以快速回滚。 本次大促前的千万级订单表加字段操作,通过pt-online-schema-change的无锁Online DDL方案落地,完全没有影响峰值期的业务运行,后续我们也把该方案作为所有超过500万行核心表的标准变更流程,彻底规避了大表锁表的业务风险。
免责声明
本工具与文章内容仅用于学习测试场景,优化建议仅供参考,请勿直接用于生产环境,由此产生的风险自行承担。