seconds_behind_master = 0 不代表主从强一致,仅说明中继日志已拉完;真实一致性需确认 exec_master_log_pos 与 read_master_log_pos 一致、slave_sql_running_state 为“waiting for more updates”,并配合 gtid 或 pt-table-checksum 校验。

主从追平后不能直接切流量
Seconds_Behind_Master = 0 只说明中继日志已拉完,不代表最后一条事务已执行完毕。SQL线程可能还在内存里排队,或刚写入 relay log 但尚未 fsync 到磁盘。真实状态要看 Exec_master_log_pos 和 Read_master_log_pos 是否一致,且 Slave_SQL_Running_State 显示 “Slave has read all relay log; waiting for more updates”。
- 执行
STOP SLAVE;后,再查SHOW SLAVE STATUS\G,确认Seconds_Behind_Master为 NULL、Exec_master_log_pos == Read_master_log_pos - 此时在源库执行
FLUSH LOGS;,记下新 binlog 文件名和位置,作为“停写边界” - 目标库执行
SELECT MASTER_POS_WAIT('xxx-bin.000001', 123456789, 30);,返回非 NULL 才代表该位置已执行
切换前必须冻结源库写入
只设 read_only=ON 不够——它不阻断 SUPER 权限用户的写操作,也不阻止触发器、事件或存储过程内的隐式写入。真正安全的冻结要分两步:先全局禁止新连接,再清空活跃事务。
- 执行
SET GLOBAL super_read_only = ON;(MySQL 5.7.8+),比read_only更严格,连 SUPER 用户也无法写 - 用
KILL命令干掉所有非系统线程:SELECT CONCAT('KILL ',id,';') FROM information_schema.processlist WHERE user != 'system user' AND command != 'Sleep'; - 最后执行
FLUSH TABLES WITH READ LOCK;,确保无任何写入残留,再立刻SHOW MASTER STATUS;记下最终位点
迁移后校验不能只看行数
COUNT(*) 相同不代表数据一致——比如某条记录被 UPDATE 两次,结果和只 UPDATE 一次在行数上没区别,但内容已不同。哈希校验也容易因字段顺序、NULL 处理、字符集隐式转换出错。
- 对核心表用
pt-table-checksum(Percona Toolkit),它基于 chunk 分片计算 CRC32,自动处理类型转换和排序差异 - 若无法装第三方工具,手动抽样比对:按主键范围分段,如
SELECT MD5(GROUP_CONCAT(CONCAT(id,':',name,':',updated_at) ORDER BY id)) FROM users WHERE id BETWEEN 10000 AND 20000; - 特别检查
TIMESTAMP字段——源库时区设置不同会导致值偏移,校验前统一用CONVERT_TZ()转成 UTC
回滚通道必须保留 binlog 并验证可读
很多人保留旧主库但忘了验证 binlog 是否真能回放。常见坑是:旧主库重启后 log_bin 被关闭、binlog 文件权限不对、或 max_binlog_size 导致文件轮转后找不到起始位点。
- 迁移后 48 小时内,旧主库保持
read_only=ON+super_read_only=ON+log_bin=ON - 执行
mysqlbinlog --base64-output=decode-rows -v /var/lib/mysql/mysql-bin.000001 | head -20,确认能解析出有效 event - 挑一个业务关键时间点(如切换前 5 分钟),用
mysqlbinlog --start-datetime="2026-08-12 17:55:00" ... | mysql -u root -p new_db测试回放通路
created_at 字段全变成 0000-00-00 —— 那不是复制问题,是源库 sql_mode 里少了 STRICT_TRANS_TABLES,早该在迁移前就发现。











