主从复制延迟十几秒,业务侧已经开始报警,登录从库一查,某张核心表的行数和主库对不上。这种场景干过 MySQL 运维的人多少都经历过。数据不一致的原因可能有很多,但收尾工作通常只有一个:把从库的数据“掰”回和主库一致。手工一条条比对、生成 UPDATE 语句再执行,小表还能忍,一旦遇到几千万行的表,这活儿就不是人干的。我这边实际项目里用的方案是 Percona Toolkit 里的 pt-table-sync,配合 pt-table-checksum 先摸清差异范围,再精准修复。这篇文章把整套思路、命令和踩过的坑完整记录下来,给同样被主从一致性问题折腾的同行一个可直接落地的参考。
1. 主从不一致的常见来源:复制机制挡不住的人为失误
在聊工具之前,先把问题的根源理清楚。很多人有一个错觉:只要主从复制线程在跑,主库和从库的数据就一定是一样的。这个想法在“一切都按理想状态运行”时成立,但现实里打破一致性的因素远比想象的多。
第一类来源是主库上非确定性操作。 如果在主库上执行了诸如 RAND()、UUID()、NOW() 这类函数,并且写入到了表中,那么在 statement 格式的 binlog 下,从库重放时执行的是同一段 SQL,但得到的结果可能不同。UUID() 每一次调用返回值都不同,这直接导致从库行数据和主库不一致。即便 binlog 格式已经改成 ROW,某些隐式转换或存储过程的边界行为还是可能埋雷。
第二类来源是人为在从库上执行了写入操作。 比如排查问题时手滑在从库上跑了一条 UPDATE,或者某个后台任务错误地连到了从库进行数据订正。MySQL 默认从库上的写入不会同步回主库,也不会被复制线程感知,但从此刻起,主备数据就已经岔开了。如果后续该表在主库上还有更新,从库重放时很可能直接报错,复制线程中断,不一致被放大。
第三类来源是主从切换演练或异常宕机。 半同步复制可以在一定程度上降低丢数据概率,但遇到超时降级、网络分区、机房断电这些场景,从库的 relay log 可能不完整,重放后部分事务丢失,数据就悄悄缺了一块。
第四类来源是版本差异。 主库 MySQL 5.7、从库 MySQL 8.0 这种混搭架构并不少见。不同版本对字符集排序规则、数字精度、日期格式的处理存在差异,某些 DDL 或 DML 在从库上重放后结果集不一致。
第五类来源是存储过程和触发器。 主库有触发器,从库也有触发器,复制时如果 binlog 格式不是 ROW,触发器可能在两端各自执行,产生双倍效果;又或者从库触发器缺失,某些补偿逻辑没有执行。
这里必须强调一个观点:半同步复制、组复制这些机制解决的是“主库提交的事务有没有安全到达从库”的问题,解决不了“从库上的数据因为各种原因被改坏之后谁来纠偏”的问题。直到今天,物理或逻辑备份恢复仍然是处理严重不一致的兜底手段,但备份恢复的时间窗口成本太高。pt-table-sync 的价值就在这里:在不重建从库的前提下,自动比对并修复主从之间的数据差异。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 修复前先圈定差异范围:pt-table-checksum 是整套方案的“探针”
直接运行 pt-table-sync 不做任何检查是一种高风险操作。工具本身确实会逐行比对,但如果表很大,这个过程会非常漫长且消耗大量 IO。所以在修复之前,我习惯先用 pt-table-checksum 做一次全库或核心表的校验,拿到一个“哪些库哪些表不一致”的清单,再针对性地跑 sync。
pt-table-checksum 的工作原理可以这样理解:它把每张表按照主键或唯一键分成多个 chunk(块),在主库上对每个 chunk 执行带校验和逻辑的 SELECT,再把同样的计算逻辑通过复制在从库上执行,最后对比主从两端计算出的校验值是否一致。不一致的 chunk 会记录到结果表中,作为后续 sync 的输入。
2.1 安装 Percona Toolkit
pt-table-sync 和 pt-table-checksum 都属于 Percona Toolkit 工具集。安装方式比较简单,官方提供了各主流发行版的源。
bash复制# CentOS / RHEL 系列
yum install https://repo.percona.com/yum/percona-release-latest.noarch.rpm
percona-release enable tools release
yum install percona-toolkit
# Ubuntu / Debian 系列
wget https://repo.percona.com/apt/percona-release_latest.$(lsb_release -sc)_all.deb
dpkg -i percona-release_latest.$(lsb_release -sc)_all.deb
apt-get update
apt-get install percona-toolkit
安装完成后,用 pt-table-checksum --version 验证一下是否正常。这套工具依赖 Perl 和 DBI 驱动,如果系统里缺依赖,安装时一般会自动解析。
2.2 准备专用的校验账号
pt-table-checksum 需要连接主库和从库,权限方面建议单独创建一个账号,避免使用 root 账号带来的安全风险。该账号需要以下权限:
- 在主库上:SELECT、PROCESS、SUPER(用于设置 binlog 格式和执行校验语句)
- 在从库上:SELECT、PROCESS、REPLICATION SLAVE
sql复制CREATE USER 'pt_checksum'@'%' IDENTIFIED BY 'YourStrongPassword';
GRANT SELECT, PROCESS, SUPER ON *.* TO 'pt_checksum'@'%';
GRANT SELECT, PROCESS, REPLICATION SLAVE ON *.* TO 'pt_checksum'@'%';
FLUSH PRIVILEGES;
SUPER 权限在 MySQL 8.0 中已经拆分,如果用的是 8.0,则需要授予 BINLOG_ADMIN 权限(在部分版本中还需要 SYSTEM_VARIABLES_ADMIN)。
2.3 执行校验并读取结果
基础命令如下:
bash复制pt-table-checksum \
--host=192.168.1.10 \
--user=pt_checksum \
--password='YourStrongPassword' \
--databases=app_db \
--replicate=percona.checksums \
--create-replicate-table \
--no-check-binlog-format \
--chunk-size=1000 \
--chunk-time=1 \
--max-load='Threads_running=50' \
--pause-file=/tmp/pt-checksum.pause \
--interval=1
参数含义拆解如下:
--replicate=percona.checksums:校验结果写入 percona 库的 checksums 表,方便事后查询。--create-replicate-table:首次运行时自动创建结果表。--chunk-size=1000:每个 chunk 的行数阈值,不是硬性限制,工具会根据实际情况调整。--chunk-time=1:每个 chunk 的目标执行时间(秒),工具会动态调整 chunk 大小,尽量把耗时控制在 1 秒左右。--max-load='Threads_running=50':当主库 Threads_running 超过 50 时暂停校验,降低对业务的影响。--pause-file:在指定文件存在时暂停校验,用于手动控制启停。--no-check-binlog-format:跳过 binlog 格式检查,如果你的 binlog_format 不是 ROW 又想跑这个工具,需要加上这个选项。但请注意,binlog 格式问题本身也是导致不一致的根源之一,后面会专门讲。
校验完成后,查询 percona.checksums 表,重点关注 this_cnt、master_cnt、this_crc、master_crc 这几个字段。其中 this_cnt 表示从库上该 chunk 的行数,master_cnt 表示主库上的行数,两个值不相等说明存在行数差异;如果行数一致但 this_crc 与 master_crc 不一致,说明行的内容有差异。
sql复制SELECT db, tbl, chunk, this_cnt, master_cnt, this_crc, master_crc
FROM percona.checksums
WHERE master_cnt <> this_cnt OR master_crc <> this_crc;
在实际生产中,我一般会把这个查询结果导出成文本,作为后续筛选修复目标的依据。校验阶段不需要对所有不一致的表都立刻执行 sync,有些表可以在后续维护窗口统一处理。
2.4 校验过程中的常见小问题
第一,连接数不足。pt-table-checksum 默认会检测从库并连接,如果从库 max_connections 设置得很小,可能直接连不上。可以先通过 --no-check-slave 跳过从库检测,但这样校验结果参考价值会降低。
第二,max_allowed_packet 太小。当一行数据中有较大的 text/blob 字段时,校验查询可能因为返回包过大而失败。一般建议主库、从库的 max_allowed_packet 至少设置为 64M。
第三,chunk 大小设置不合理。--chunk-size 不是越大越好,太大的 chunk 会导致一次校验执行时间过长,产生长事务和主从延迟。我建议在测试环境先用 --chunk-size=500 跑一遍,观察 SHOW SLAVE STATUS 中的 Seconds_Behind_Master 是否有明显上升,再决定是否调大。线上最稳妥的做法是使用 --chunk-time 让工具自动调整。
3. pt-table-sync 的修复逻辑:它到底是怎么“对齐”主从数据的
很多人把 pt-table-sync 理解成一个“自动执行 SQL 的工具”,这个表述不算错,但容易让人忽略它在执行前做了多少判断。只有理解了它的工作原理,再排查执行过程中的问题才能有的放矢。
3.1 分层比对,先元数据后数据
pt-table-sync 在执行时,会先比对主从两端的表结构。如果表结构不同,它会直接报错或有条件地执行 ALTER(需要开启 --[no]check-triggers、--[no]check-foreign-keys 等相关检查)。这一步很关键,因为如果表结构都不一致,后续的数据比对没有意义。常见的做法是先手动把表结构改到一致,再执行 sync,尽量避免让工具自动改表结构。
确认表结构一致后,工具会获取表的主键或唯一键。没有任何索引的表无法通过该工具进行高效比对,工具会直接拒绝执行,并提示需要主键或唯一索引。这一点在生产环境中几乎没有问题,但如果你手里有某些历史遗留的无主键大表,要先在建表层面解决。
3.2 chunk 切分与双向校验
工具会根据主键把表切分为多个 chunk,类似 pt-table-checksum 的做法。在每个 chunk 上,它会在主库和执行目标(从库)上分别读取数据行,逐列比较。发现差异行后,工具会根据差异类型生成修复 SQL:
- 主库有、从库没有的行 → 生成 INSERT 语句
- 主库没有、从库有的行 → 生成 DELETE 语句
- 两边都有、但某些列的值不同 → 生成 UPDATE 语句
这里有个容易误解的点:pt-table-sync 生成的修复 SQL 基于“以主库数据为准”的原则。也就是说,它默认用户传入的第一个 host 是主库,后面列出的 host 是从库。如果参数传反了,工具会试图把主库的数据改成和从库一致,那可就是事故了。
3.3 变更写入策略
对于 INSERT 和 UPDATE,工具提供了 --replace 选项。不带该选项时,工具会生成 INSERT ... ON DUPLICATE KEY UPDATE 或 REPLACE INTO 语句(取决于版本和参数选择);带上 --replace 后,会优先使用 REPLACE INTO。两者的差异在于 REPLACE 遇到主键冲突时会先 DELETE 再 INSERT,如果有外键引用或自增 ID,可能引发不必要的副作用。所以不特别需要的情况下,我不会主动加 --replace。
部分版本还支持 --insert-ignore,在遇到冲突时直接忽略新插入,这个一般在以从库为准的场景下才用得到,主从修复中很少碰。
3.4 执行方式:print 还是 execute
pt-table-sync 默认只打印生成的 SQL 而不实际执行,这个设计非常高明,相当于强制你做一次人工 review。真正执行时需要显式加上 --execute。
我的操作习惯是:先 --print 输出全部 SQL 到文件,人工检查前几条 SQL 是否正确(尤其是 DELETE 语句是否有 where 条件,UPDATE 语句是否只改了差异列),确认无误后再追加 --execute 执行。
3.5 针对大表的高效校验参数
处理千万行级别的表时,全表逐行比对是不现实的。工具提供了两个关键参数来控制校验粒度:
--chunk-size:逻辑行数阈值,控制每个 chunk 的行数--chunk-time:目标执行时间阈值,工具会根据实际执行耗时动态调整 chunk 大小
两者同时设置时,工具会以能同时满足两个条件的最大 chunk 为准。生产环境我建议主要用 --chunk-time,让工具自动适配负载。手动写死 --chunk-size 反而容易在性能波动时造成单一 chunk 过大。
工具还有 --txn-size 参数,用于控制每个事务中处理的行数。默认情况下它和 chunk 大小相关,但在一些有显式事务要求的场景下可以单独调整。值越小,每次事务持有行锁的时间越短,对在线业务影响越小,但整体修复时间会变长。这个参数在修复那些频繁更新的热表时非常有用。
4. 实操全程:从 dry-run 到线上 execute 的完整链路
下面以一次真实的修复过程为例,完整过一遍从环境检查到最终验证的操作步骤。这次要修复的是一个订单相关库 app_db 下的 orders 表,在主从之间出现了 3000 多行差异。
4.1 修复前检查环境
操作之前,我习惯先确认几件事:
bash复制# 确认主从复制状态正常,没有 SQL 线程报错
mysql -h 192.168.1.10 -u admin -p -e "SHOW SLAVE STATUS\G" | grep -E "Slave_IO_Running|Slave_SQL_Running|Seconds_Behind_Master"
# 确认 binlog 格式
mysql -h 192.168.1.10 -u admin -p -e "SELECT @@binlog_format;"
# 确认主从两端的表结构一致
mysql -h 192.168.1.10 -u admin -p -e "SHOW CREATE TABLE app_db.orders\G"
mysql -h 192.168.1.11 -u admin -p -e "SHOW CREATE TABLE app_db.orders\G"
Seconds_Behind_Master 不为 0 时,最好等复制追平再操作。因为如果从库还在追 data,pt-table-sync 在读取时可能拿到中间态数据,修复结果自然不准。
4.2 备份涉及的表
任何修复操作都有风险,备份这一步不能省。对于几百 GB 的大表,用 mysqldump 全量导出一个文件的方案太慢,我通常用下面两种方式之一:
- 用
CREATE TABLE ... AS SELECT把目标表复制到一个备份库,但这会丢失索引,适合小表。 - 用 pt-archiver 或者按主键范围分批导出差异数据,适合大表。
对于差异行数只有几千的情况,最稳妥的做法是先通过 pt-table-checksum 的 chunk 结果定位差异 chunk,再把这些 chunk 的数据单独导出。这样备份文件很小,恢复也快。
4.3 先跑一次 print,观察生成的 SQL
bash复制pt-table-sync \
--host=192.168.1.10 \
--user=pt_sync \
--password='YourStrongPassword' \
--databases=app_db \
--tables=orders \
--replicate=percona.checksums \
--print \
> /tmp/sync_orders.sql
这里用 --replicate=percona.checksums 而不是直接全表扫描,目的是复用 pt-table-checksum 已经计算好的 chunk 边界。工具会直接定位到差异 chunk,避免重新 scanning 整张表。尤其是在大表场景下,这一步能把执行时间从小时级降到分钟级。
打开 /tmp/sync_orders.sql,重点关注几个方面:
- DELETE 语句是否带完整的 WHERE 条件(主键或唯一键条件);
- UPDATE 语句是否只修改了差异列,而不是把所有列都更新一遍;
- INSERT 语句的列清单是否完整。
以我之前的经验,生成的 DELETE 语句长这样:
sql复制DELETE FROM `app_db`.`orders` WHERE `id` = '104857' LIMIT 1;
UPDATE 语句会带上所有差异列的新值和完整的主键条件。LIMIT 1 是工具加的保护条件,确保一条语句只影响一行,避免误删多行。
4.4 正式执行修复
确认 SQL 没有问题后,把 --print 换成 --execute:
bash复制pt-table-sync \
--host=192.168.1.10 \
--user=pt_sync \
--password='YourStrongPassword' \
--databases=app_db \
--tables=orders \
--replicate=percona.checksums \
--execute
执行过程中,工具会输出进度信息到标准错误或日志。我一般会提前开启另一个终端,实时监控主从状态:
bash复制watch -n 2 'mysql -h 192.168.1.10 -u admin -p -e "SHOW SLAVE STATUS\G" | grep -E "Seconds_Behind_Master|Slave_SQL_Running"'
如果发现 Seconds_Behind_Master 飙升,说明工具产生的主库写入量对复制产生了压力。此时可以 Ctrl+C 中断,调小 --chunk-size 或 --txn-size,重新执行。pt-table-sync 本身是事务型的,中断后未提交的 chunk 不会生效,已经提交的 chunk 不会回滚,所以可以安全重跑。
4.5 执行后的结果验证
修复完成后,再次运行 pt-table-checksum,确认同一张表的 checksum 结果全部一致。这个步骤非常关键,如果 skip 掉了,等于没有闭环。
bash复制pt-table-checksum \
--host=192.168.1.10 \
--user=pt_checksum \
--password='YourStrongPassword' \
--databases=app_db \
--tables=orders \
--replicate=percona.checksums \
--create-replicate-table
查询结果表:
sql复制SELECT db, tbl, chunk, this_cnt, master_cnt, this_crc, master_crc
FROM percona.checksums
WHERE db='app_db' AND tbl='orders'
AND (master_cnt <> this_cnt OR master_crc <> this_crc);
查询返回 0 行,说明 orders 表的主从数据已经完全一致。到这里,一次完整的修复流程才算真正走完。
5. 实战中容易翻车的几个细节:binlog 格式、触发器、大表与中断恢复
前面讲的流程只能算是“标准路线”,生产环境和测试环境最大的区别在于:各种意外才是常态。这里把我实际踩过的坑,按出现频率和破坏力排序,一条条列出来。
5.1 binlog_format 对修复结果有决定性影响
pt-table-sync 在 ROW 格式 binlog 下工作是安全的。它生成的 UPDATE/DELETE/INSERT 语句在主库执行后,会以 ROW 格式写入 binlog,从库重放时是基于行变更的,结果精准。
但如果主库 binlog_format 是 STATEMENT,问题就麻烦了。工具生成的 DELETE 语句虽然带了主键条件,但在 STATEMENT 格式下,从库重放时执行的是 SQL 语句,而不是行变更。如果从库的数据本身有差异,这条 DELETE 在从库上匹配的行可能不是主库上预期的那一行。更严重的是,某些带有函数或隐式转换的 SQL 在两端执行结果可能完全不同。
所以修复前务必确认 @@binlog_format=ROW。如果因为各种历史原因不能改成 ROW,至少要在执行 sync 时使用 --no-check-binlog-format 跳过检查,并且接受一定的不确定性风险。但我的建议是:这种情况先改 binlog 格式,把基础打好,再谈修复。
5.2 触发器会让修复“越修越乱”
这是非常隐蔽的一个坑。假设 orders 表在主库上有一个 AFTER INSERT 触发器,用于往 order_log 表写日志。pt-table-sync 在主库上执行 INSERT 修复 orders 表时,会正常触发这个触发器,向 order_log 写入一条记录。问题是,这属于主库上的一次正常业务写入,因此 binlog 里会记录两条变更:orders 的插入和 order_log 的插入。从库重放时,如果从库上 orders 表也有同样的触发器,那么从库上的触发器会被再次触发,order_log 表就被写入了两次。结果就是,orders 表修复了,order_log 表又变得不一致了。
工具对此有对应的处理参数:--[no]check-triggers 和 --[no]triggers。默认情况下,pt-table-sync 会检查表上是否存在触发器,如果发现触发器且 binlog_format 为 ROW,它可能会建议你使用 --no-triggers 来让工具在从库执行时忽略触发器,只重放主库 binlog 中已有的 row 变更。
我实际处理过的情况是:主从两端的 orders 表都有触发器,我的做法是临时禁用从库上的触发器,或者干脆先把触发器 drop 掉,等数据修复完成后再重建。在业务低峰期做这种操作是可控的。
5.3 检查从库复制过滤规则
如果从库配置了 replicate-ignore-db、replicate-do-table 这类复制过滤规则,那么 pt-table-sync 在主库上执行的修复 SQL 在从库上可能不会生效,或者只会部分生效。工具默认会检查复制过滤规则并提示风险。如果你确认从库的过滤规则不会影响到目标表,可以加上 --no-check-replication-filters 跳过检查。但这里我建议多留一个心眼:过滤规则本身就是导致主从不一致的高频原因,修复之前先弄清楚为什么配了过滤规则,不要为了绕过检查而绕过检查。
5.4 大表修复的时长失控
我之前遇到过一张 2 亿行的流水表,pt-table-checksum 跑下来需要近两个小时,pt-table-sync 如果使用 --replicate 只处理差异 chunk,时间还能接受;但如果差异 chunk 很多,或者没有用 --replicate 而是全表扫描,时间就会暴涨。
处理大表的经验:
- 提前用
EXPLAIN确认目标表的主键索引可以被正常使用。 - 设置合理的
--chunk-time,建议先从--chunk-time=0.5开始试,逐渐调大,找到当前负载下最优值。 - 用
--txn-size=100控制每个事务的行数,避免长事务导致主库锁和 undo log 膨胀。 - 考虑分多次修复:第一次处理差异最大的分区或时间段,第二次再处理剩余部分。可以通过
--where参数按主键范围或时间范围拆分。
bash复制# 示例:只修复 2023-01-01 到 2023-01-31 的数据
pt-table-sync \
--host=192.168.1.10 \
--user=pt_sync \
--password='YourStrongPassword' \
--databases=app_db \
--tables=orders \
--where="create_time >= '2023-01-01' AND create_time < '2023-02-01'" \
--execute
5.5 修复中断后的恢复
pt-table-sync 的 --execute 是逐 chunk 提交的。意外中断时,已提交的 chunk 不会自动回滚。重新执行时,工具会重新计算差异,所有 chunk 从头开始,但已经修复过的 chunk 在比对后差异为 0,会被跳过,所以整体耗时不会增加太多。实际上可重入性很好,不用太担心中断。
但如果中断发生在主库上已经执行了部分 binlog 写入、而从库还没有完全重放的时刻,重新执行前最好先看一眼复制延迟是否追上。否则从库读取的仍是旧数据,修复主库后反而会导致之后从库重放时出现重复键或找不到待删除行的错误。
5.6 修复路由表或分库分表中间件场景
如果业务用的是 ShardingSphere、MyCat 这类中间件,pt-table-sync 直接连接后端 MySQL 实例的方式依然有效,但需要注意:中间件可能修改表名或路由规则,直接连后端实例进行修复前,要确认你操作的那张表真的是目标分片所在的表。如果表被分片到多个实例,需要对每个分片实例分别执行 sync,不能用一条命令覆盖所有分片。这类环境下,我一般会先咨询中间件维护方,确认分片键和实例映射关系,再决定修复策略。
6. 别等出问题才动手:一致性巡检与参数层面的预防
修复只是止血,长期来看还是要建立预防机制。我这边实践下来的做法是:定期巡检 + 权限管控 + 参数规范,三层防护。
6.1 定期执行 pt-table-checksum
用 crontab 定时跑校验任务,结果写入独立的 checksum 库,再通过脚本检测不一致的表并推送告警。以下是改造后的定时任务计划:
bash复制# 每周一凌晨 1 点对核心库做全量校验
0 1 * * 1 /usr/bin/pt-table-checksum --host=192.168.1.10 --user=pt_checksum --password='xxx' --databases=app_db --replicate=percona.checksums --chunk-time=1 --max-load='Threads_running=50' --pause-file=/tmp/pt-checksum.pause >> /var/log/pt-checksum.log 2>&1
# 每月 1 日凌晨 3 点对所有库做全量校验
0 3 1 * * /usr/bin/pt-table-checksum --host=192.168.1.10 --user=pt_checksum --password='xxx' --replicate=percona.checksums --chunk-time=2 --max-load='Threads_running=40' --pause-file=/tmp/pt-checksum.pause >> /var/log/pt-checksum-monthly.log 2>&1
校验结果不能只停留在静态表里,要配合一个简单的监控脚本,定期扫描 percona.checksums 表,发现不一致就推送告警到钉钉或企业微信。脚本逻辑不复杂,但价值很大——能把“主从数据可能在坏”从一个不可见的状态变成可量化、可告警的状态。
6.2 从库写入权限管控
很多不一致是人为在从库执行了写操作导致的。在从库上把除运维账号外的所有账号的 INSERT/UPDATE/DELETE 权限全部回收,能在源头大幅降低这类风险。如果业务上确实有“读写分离但读库需要单独修改某些配置表”的需求,那就用白名单方式单独放行,不要放开所有表的写权限。
6.3 参数层面的防护配置
以下是我在部署 MySQL 主从时强制要求的参数基线,它们不能保证数据 100% 一致,但可以显著降低不一致的概率:
| 参数名 | 建议值 | 说明 |
|---|---|---|
| binlog_format | ROW | 行级复制,避免 statement 格式下 SQL 重放结果不一致 |
| binlog_row_image | FULL | 确保 binlog 中记录完整行数据,便于工具比对和修复 |
| log_slave_updates | ON | 从库记录自己的 binlog,便于从库再级联或作为新主库 |
| slave_parallel_workers | 按 CPU 核数调整 | 适当并行回放,减少延迟堆积 |
| sync_binlog | 1 | 每次提交后同步 binlog 到磁盘,降低宕机丢 binlog 风险 |
| innodb_flush_log_at_trx_commit | 1 | 每次事务提交后刷 redo log,与 sync_binlog=1 配合 |
| gtid_mode | ON | 使用 GTID 复制,方便定位事务和切换 |
binlog_row_image 这个参数容易被忽略。默认值是 FULL,但如果被改成了 MINIMAL,binlog 里只记录变更列和必要的主键列,pt-table-checksum 和 pt-table-sync 在比对时可能拿不到完整的行快照,某些场景下会误判为数据不一致。
6.4 修复工具不能替代备份策略
最后必须强调一个原则:pt-table-sync 是修复工具,不是备份工具。任何对大表执行修复的场景,都必须在执行前做好备份。如果备份策略本身没有恢复验证过,那么即使修复过程中出了问题也无法快速回滚。我的习惯是在每一轮大版本变更或数据订正之后,至少做一次恢复演练,确认备份文件真的能拿来用。
对照我自己的经验,这套“先 pt-table-checksum 圈范围、再 pt-table-sync 精准修复、后定期巡检预防”的组合,已经处理过单表差几千万行的极端场景,也处理过只差几行的小问题,整体可操作性相当强。如果你正在被主从数据不一致的问题折腾,不妨先在你的一台测试从库上跑一遍,把工具的逻辑和参数吃透,再上生产。这样即便过程中出现意外,你也能快速反应。
