“主从复制是自动的,所以主从数据一定一致”——这是运维里最大的错觉之一。binlog 丢失、复制中断、人为误操作、半同步降级、甚至程序 BUG,都会让从库与主库悄然分道扬镳。更隐蔽的是:复制通常不会报错,不一致像蛀虫一样潜伏,直到某个深夜的查询突然返回错误数据。数据一致性校验与修复,就是给这套"自动复制"装上体检与治疗机制:周期性地对主从做逐块校验,发现差异后精确修复。本指南深度讲解一致性校验的原理(chunk 划分、checksum、校验时机)、工具实战(pt-table-checksum / pt-table-sync)、增量修复与对账平台,以及主从以外的通用一致性验证方法。
一、为什么主从会不一致
1.1 不一致的根源
主从不一致的常见根因:
· binlog 格式问题(非 ROW,或 mixed 下函数非确定性)
· 从库复制中断后手动补数错位
· 从库被误写(人为/程序绕过只读)
· 半同步降级为异步期间的主从窗口
· 主库崩溃未完全 flush 导致的差异
· 存储引擎差异 / 表结构不一致
· 大事务 / 长延迟期间的并发写覆盖
关键事实:
· MySQL 复制"尽力而为",不保证强一致
· 不校验 = 不一致可能在未知时间点悄悄产生
→ 一致性校验是"复制健康"的体检,必须周期性做
ℹ️ 核心洞察:复制协议的语义是"主库执行什么,从库执行什么"(statement)或"主库改了哪些行,从库改哪些行"(row),但它不校验结果是否一致。校验必须由外部工具按"对账"思路完成。
1.2 校验的两种思路
① 抽样对比:随机抽行/按 id 抽样,速度快,有盲区
② 全量对账:逐块比对(chunk + checksum),彻底,慢
生产实践:全量对账为主(周期性),抽样为辅(日常快速巡检)
二、chunk 校验原理
2.1 为什么不能直接 COUNT
-- 错误示范:主从各 COUNT
SELECT COUNT(*) FROM orders; -- 主库
SELECT COUNT(*) FROM orders; -- 从库
-- 行数相等 ≠ 数据一致(可能内容不同,COUNT 不看内容)
-- 且大表 COUNT 全表扫描,代价极高
2.2 chunk 划分与 checksum
pt-table-checksum 的核心算法:
1. 按主键把表切成多个 chunk(每 chunk 约 1000-2000 行)
2. 对每个 chunk 计算 checksum(行内容哈希)
3. 主从都算同一 chunk 的 checksum 并对比
4. 差异 chunk → 精确到行定位
chunk 划分好处:
· 内存可控(每次只处理一小块)
· 锁影响小(只锁 chunk 范围)
· 可增量/可断点续跑
· 主从并行对比,吞吐高
-- pt-table-checksum 本质执行的 SQL(示意)
-- 主库计算 chunk 校验:
SELECT CRC32(CONCAT_WS('#', id, name, amount, ...))
FROM orders
WHERE id BETWEEN ? AND ?;
-- 从库同样计算并对比
2.3 checksum 的选择
checksum 函数:
· CRC32:快,碰撞概率低(32bit)
· MD5 / SHA:更稳但更慢
· pt-table-checksum 默认组合算法(F CRC32)
注意:
· 浮动/浮点列可能因平台差异产生不一致(用 round)
· 时间列注意时区差异
· 大字段(BLOB/TEXT)用 CRC32(CONCAT(bit_count...)) 处理
三、pt-table-checksum 实战
3.1 基本用法
# 全库校验(dsn 指向主库,工具自动感知从库)
pt-table-checksum \
--host=mysql-master \
--user=check \
--password=*** \
--databases=orders_db \
--tables=orders \
--replicate=percona.checksums \
--no-check-binlog-format \
--chunk-size=1000 \
--empty-replicate-table
# 输出解读:每行一个表,TS/DIFFS/ROWS/CHUNKS
# DIFFS>0 表示有差异 chunk
3.2 校验结果表
-- 校验结果存在 percona.checksums 表
SELECT * FROM percona.checksums
WHERE master_cnt <> this_cnt OR master_crc <> this_crc
LIMIT 10;
-- 每个 chunk 一行:master_crc(主库校验值) vs this_crc(当前库)
3.3 常用参数与策略
# 控制负载与频率
--max-lag=1 # 从库延迟超 1s 自动等待
--check-interval=1 # 检查从库延迟间隔
--chunk-size=1000 # 每 chunk 行数(越大越快但锁更久)
--chunk-size-limit=4 # 动态 chunk 上限
--max-load Threads_running=200 # 主库负载过高自动节流
--lock-wait-timeout=30
# 表过滤
--databases / --tables / --ignore-tables
# 跳过不确定列
--no-check-binlog-format / --nocheck-replication-filters
# 典型调度(低峰 + 大表分表跑)
0 3 * * 0 pt-table-checksum --databases=core_db --max-lag=2 > /var/log/consistency/checksum.log
3.4 权限要求
-- 校验账号所需权限
GRANT SELECT, PROCESS, SUPER, REPLICATION SLAVE ON *.* TO 'check'@'%';
-- SUPER/PROCESS 用于 SHOW SLAVE STATUS 检测延迟与锁等待
四、发现差异后的定位
4.1 定位差异行
-- 用 checksums 表定位差异 chunk 的 id 范围
SELECT db, tbl, chunk, lower_boundary, upper_boundary,
master_cnt, this_cnt, master_crc, this_crc
FROM percona.checksums
WHERE db='orders_db' AND tbl='orders' AND master_crc <> this_crc;
-- 然后精确对账差异范围:
-- 主库
SELECT * FROM orders WHERE id BETWEEN ? AND ? ORDER BY id;
-- 从库
SELECT * FROM orders WHERE id BETWEEN ? AND ? ORDER BY id;
-- 逐行对比
4.2 差异类型判断
| 现象 | 类型 | 处理 |
|---|---|---|
| 主库有行,从库没有 | 缺失行 | 主→从 插入 |
| 从库有行,主库没有 | 多余行 | 从库删除 |
| 两库都有但内容不同 | 值差异 | 以主库为准覆盖 |
| 主库 index 差异 | 结构不一致 | 重建索引 |
五、pt-table-sync 修复实战
5.1 安全执行
# 先 dry-run 看会改什么(强烈建议)
pt-table-sync \
--execute \
--dry-run \
--replicate=percona.checksums \
--tables orders_db.orders \
D=orders_db,h=mysql-master,u=check,p=***
# 确认无误后真正执行(仅在差异 chunk 上做最小 DML)
pt-table-sync \
--execute \
--replicate=percona.checksums \
--tables orders_db.orders \
D=orders_db,h=mysql-master,u=check,p=***
5.2 修复的两种方式
方式一:基于 checksums 表定位(推荐)
只修有差异的 chunk → 最小化影响
--replicate=percona.checksums 让工具"按差异点修复"
方式二:全表同步(慎用)
不指定 replicate,逐表逐行对齐
→ 适合差异少、可短锁的表
原则:
· 以主库为准(数据一致性锚点 = 主库)
· 修复动作 = 把从库改到与主库一致
· 先备份差异行再做修复(可回滚)
5.3 修复期间的注意事项
# 修复工具会持有锁(DML + 行锁),选择低峰执行
# 修复前:
# 1) 记录差异清单
# 2) 评估差异行数(大差异 = 先复盘根因再修)
# 3) 若差异来自"复制断流",先恢复复制
# 修复后:
# 1) 重新跑 checksum 验证
# 2) 检查复制延迟/错误
# 3) 分析根因并加防护(只读、延迟监控)
六、复制恢复与前置条件
6.1 修复前先恢复复制健康
-- 检查复制状态
SHOW REPLICA STATUS\G;
-- 常见问题:SQL 线程停止(错误码 1062 主键冲突等)
-- 恢复方式:
-- 小额差异:STOP REPLICA; SET GLOBAL SQL_SLAVE_SKIP_COUNTER=1; START REPLICA;
-- 严重差异:需 re-position / 重建从库
⚠️ 硬规则:如果从库复制已中断,先恢复复制再对账。在断流状态下修复 = 修复结果会被随后的追赶写入再次覆盖,徒劳且制造新差异。
6.2 重建从库兜底
# 差异过大 / 无法增量修复时:重建从库
# 用备份(Xtrabackup)+ binlog 增量搭从库
xtrabackup --backup --target-dir=/backup
# 传到新从库,恢复后 change master 到主库
七、自动化对账平台
7.1 架构
对账平台组成:
· 调度器:周期性触发校验(cron / 工作流)
· 校验执行:pt-table-checksum 或自研 chunk 校验
· 结果存储:差异明细入库(audit 表)
· 告警:差异数 > 0 → 通知 DBA
· 修复流程:人工确认 → pt-table-sync / 自研修复
· 报表:校验覆盖率、差异趋势
触发策略:
· 全量校验:每周/每月(低峰)
· 增量校验:每日对变化大的表
· 事件触发:复制异常恢复后立即校验
7.2 校验覆盖矩阵
校验对象分层:
· 核心表(订单/账户/余额):高频率 + 全量
· 普通表:中频率 + 抽样
· 历史/归档表:低频 + 抽样
· 结构一致性:定期对比 SHOW CREATE TABLE
7.3 指标化监控
# 对账平台指标
consistency_check_total # 校验次数
consistency_diff_chunks{db,tbl} # 差异 chunk 数
consistency_diff_rows # 差异行数
consistency_repair_duration # 修复耗时
consistency_repair_failed # 修复失败
# 告警:任何 db.tbl 的 diff_rows > 0
八、超越 MySQL 的一致性校验
8.1 PostgreSQL / 其他引擎
· PostgreSQL 逻辑复制/流复制:
可用 pg_stat_replication 观察 WAL 接收进度,
数据一致性用自定义 checksum 查询或 pg_dump 对比抽样
· Redis 多副本:redis-check / keyspace 对比
· 消息队列(Kafka):offset 对齐是"消费进度"而非数据一致,
需独立对账(去重 + 幂等消费)
8.2 通用数据对账设计
# verify_service.py — 通用 chunk 对账框架
def verify_table(source, target, table, chunk_size=1000):
"""按主键范围逐 chunk 对比两张表。"""
min_id, max_id = source.range(table)
diffs = []
for lo in range(min_id, max_id + 1, chunk_size):
hi = min(lo + chunk_size - 1, max_id)
s = source.rows(table, lo, hi) # 主库 chunk
t = target.rows(table, lo, hi) # 从库 chunk
if s != t:
diffs.append(find_diff_rows(s, t, table))
return diffs
# 幂等修复(以 source 为准,可重放)
def repair(s, t, table, key, field):
for row in s:
if t.get(key=row[key]) is None or t.get(key=row[key])[field] != row[field]:
t.upsert(table, row) # 幂等:重复执行结果一致
九、实践清单与避坑
9.1 落地 Checklist
□ 校验账号与权限准备
□ 低峰窗口排期(全量 vs 增量)
□ pt-table-checksum 参数调优(max-lag/load 节流)
□ 差异定位流程(checksums 表 → 行级对比)
□ 修复前备份差异行
□ 修复后重校验
□ 对账结果入库 + 告警
□ 根因复盘(为何不一致)+ 防护加固
9.2 常见坑
| 坑 | 现象 | 对策 |
|---|---|---|
| 复制断流时校验 | 差异持续增长 | 先恢复复制 |
| 大表校验拖垮主库 | 负载飙升 | chunk + max-load 节流 |
| 浮点/时区差异 | 误报差异 | 规范列类型/时区 |
| 修复中写冲突 | 锁等待 | 低峰 + 幂等重试 |
| 半同步降级未感知 | 差异窗口 | 监控 semi-sync 状态 |
| 校验一次就算完 | 新差异积累 | 周期化 + 覆盖矩阵 |
9.3 一致性黄金原则
· 主库是真相源,修复永远以主库为准
· 先恢复复制健康,再谈对账
· 校验要周期化,不是一次性
· 修复要可回滚、幂等
· 每个差异都要追根因(工具只治病,不治病根)
总结:一致性校验决策表
| 环节 | 关键动作 |
|---|---|
| 时机 | 周期全量 + 每日增量 + 事件触发 |
| 方法 | chunk 划分 + checksum 对账 |
| 定位 | checksums 表 → 行级 diff |
| 修复 | pt-table-sync / 幂等修复,主库为准 |
| 兜底 | 差异过大重建从库 |
| 平台 | 调度 + 存储 + 告警 + 报表 |
| 根因 | 复制健康 + 防护加固 |
主从不一致不是"复制系统坏了",而是复制的天然盲区被时间放大。一致性校验的意义在于把这个盲区变成可观测、可度量的指标——每周跑一遍对账,差异从"未知"变"已知",从"潜伏"变"已修复"。落地守住四件事:先恢复复制再对账、chunk 加节流防拖垮主库、修复以主库为准且可回滚、每次差异追根因。把"校验→定位→修复→复盘"做成周期化闭环,你的数据层就从"相信它一致"升级为"验证它一致"。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。