MySQL分批次迁移需严控主从延迟雪崩:按从库吞吐调优批次大小(如300 QPS反推行数),每批后等待Seconds_Behind_Master=0,禁用INSERT...SELECT,用GTID+时间戳双重锚定回滚点,加唯一标记字段便于binlog过滤,TIMESTAMP字段须统一时区或转DATETIME,旧库设read_only并用mysqldump快照保障回滚可靠性。

MySQL分批次迁移时怎么避免主从延迟雪崩
主从延迟在分批迁移中不是“会不会出现”,而是“什么时候爆”。一旦单批次写入量超过从库SQL线程处理能力,Seconds_Behind_Master会跳涨,后续批次再压上去,延迟就滚成分钟级甚至小时级。
- 批次大小必须按从库实际吞吐调优,不是按源库算的。建议先用
pt-heartbeat测出从库稳定写入上限(比如 300 QPS),再反推每批最多改多少行 - 每批执行后必须显式等待延迟归零:
SELECT MASTER_POS_WAIT(...)或轮询SHOW SLAVE STATUS中的Seconds_Behind_Master = 0 - 禁用
INSERT ... SELECT这类隐式大事务,它会在从库串行重放整个结果集,极易卡死。改用带LIMIT的循环插入
常见错误现象:迁移脚本跑得飞快,但监控里从库延迟曲线像心电图——每次发一批,延迟猛拉高,回落一半又来下一批,最终积压不可逆。
回滚点设计不能只靠 binlog position
mysqlbinlog 解析出来的 position 看似精确,但在 GTID 模式、多源复制、或启用了 log_slave_updates=OFF 的从库上,position 可能根本不可复现。
- 必须在每批次开始前,用
SHOW MASTER STATUS记下File和Position,同时用SELECT @@GLOBAL.gtid_executed备份 GTID 集合 - 回滚脚本不能只依赖单个 position,要结合 GTID 范围 + 时间戳(比如
mysqlbinlog --start-datetime="2024-05-20 14:00:00")做双重锚定 - 所有 DML 批次必须加唯一标记字段(如
/<em> migrate_batch_id=20240520_003 </em>/),方便紧急时用mysqlbinlog --base64-output=DECODE-ROWS过滤并重放/跳过
性能影响:记录 GTID 和打标记几乎无开销;但反复查 SHOW MASTER STATUS 和解析 binlog 是 IO 密集操作,建议用本地临时表缓存,别每次远程连。
跨版本迁移时 TIMESTAMP 字段的隐式行为差异
MySQL 5.6 升 8.0 迁移中,TIMESTAMP 字段常在回滚后“时间漂移”——不是数据丢了,是默认时区和自动更新逻辑变了。
- MySQL 5.6 默认用系统时区解释无时区标记的
TIMESTAMP值;8.0 改为用time_zone系统变量,且默认值可能是+00:00 - 如果迁移前没统一设置
time_zone='+08:00',又没在 INSERT 语句里显式带上时区(如'2024-05-20 14:00:00'+08:00),回滚重放时就会错位 - 更稳妥的做法:迁移前把所有
TIMESTAMP改成DATETIME,或统一用CONVERT_TZ()校准后再写入
容易踩的坑:只校验主键和数值字段一致性,忽略时间字段的“看起来一样但语义不同”问题,上线后发现订单创建时间全乱了。
紧急回滚失败时,怎么快速切回旧库而不丢数据
回滚失败最怕的不是慢,是“两边都脏”——新库改了一半,旧库又被新流量写入,再切回去就冲突。
- 迁移期间,旧库必须设为只读:
SET GLOBAL read_only = ON,且确保应用层没绕过(比如用 root 或其他高权限账号) - 新库上线前,用
mysqldump --single-transaction --master-data=2快照一份“最终状态”,比 binlog 更可靠——它包含完整表结构+数据+位置信息 - 如果回滚卡在半途,优先停掉所有写入,用
FLUSH TABLES WITH READ LOCK锁住旧库,再从快照恢复,而不是硬怼 binlog
复杂点在于:read_only 对 SUPER 权限用户无效,所以必须检查应用连接账号权限,不能只信配置项。


















