最稳方式是直接查information_schema.tables的table_rows估算值并比对主从差异,而非count(*)或checksum table;需同一时刻执行、排除系统库、关注5%偏差阈值,并用pt-table-checksum校验实际行数。

直接查 information_schema.tables 行数最稳
主从表行数不一致是数据漂移的第一信号,但不能靠 COUNT(*) 逐表跑——大表会锁表、拖慢复制、还可能因 MVCC 快照不一致导致主从结果不同。真正快且准的方式是绕过实际扫描,直接读元数据。
在主库和从库分别执行:
SELECT table_schema, table_name, table_rows
FROM information_schema.tables
WHERE table_schema NOT IN ('mysql','information_schema','performance_schema','sys')
AND table_type = 'BASE TABLE'
ORDER BY table_schema, table_name;
注意:table_rows 是 InnoDB 的估算值(MyISAM 才是精确值),但主从之间只要估算逻辑一致,差值就具备可比性。若某张表主从 table_rows 相差超过 5%,基本可判定存在写入偏差或复制中断。
- 必须在两库**同一时刻**执行该 SQL,避免主库写入、从库还没追上造成的“假差异”
- 不要用
SHOW TABLE STATUS替代——它输出格式不规整,难做 diff;且部分版本中Rows字段为空 - 如果某表
table_rows = 0但实际有数据,说明该表被 ANALYZE TABLE 刷新过统计信息失败,需手动触发ANALYZE TABLE db.tbl
用 pt-table-checksum 快速定位行数异常的表
pt-table-checksum 默认会把每张表按 chunk 分片校验,并在结果里带 ROWS 字段——这其实是它实际扫描的行数,比 information_schema.tables.table_rows 更接近真实值,且主从结果可严格比对。
执行后检查输出列:
TS ERRORS DIFFS ROWS CHUNKS SKIPPED TIME TABLE 06-15T14:22:01 0 0 12890 13 0 0.421 test.orders
关键看 ROWS 是否主从一致。如果主库显示 ROWS=12890,但从库显示 ROWS=12887,说明至少有 3 行没同步过去,且 DIFFS=0 意味着这 3 行可能是空值、NULL 或时间戳精度问题导致的哈希不一致,而非内容差异。
- 加
--no-check-binlog-format避免因 binlog 格式不兼容报错中断 - 加
--replicate=test.checksums把结果写进指定库表,方便后续用pt-table-sync --sync-to-master对比修复 - 别信
DIFFS=0就万事大吉——ROWS不等本身已是风险信号,尤其出现在订单、账户类核心表上
为什么 COUNT(*) 在主从上结果可能不同?
不是 bug,是 MVCC 和复制机制共同作用的结果。主库执行 COUNT(*) 时看到的是最新已提交事务;而从库 SQL 线程回放 Binlog 是单线程串行执行,中间可能卡在某个事务上,导致 COUNT(*) 查询恰好落在“半同步”状态:部分行已写入,但事务尚未 COMMIT,MVCC 快照看不到它们。
- 主库刚插入 100 行并 COMMIT,从库 SQL 线程还在回放前一条 UPDATE,此时在从库执行
COUNT(*)可能少几十行 - 主从
COUNT(*)差异持续存在(比如始终差 1~3 行),大概率是某条 INSERT IGNORE / REPLACE INTO 导致主库跳过、从库报错跳过,或主库用了INSERT ... ON DUPLICATE KEY UPDATE但从库引擎为 MyISAM(不支持该语法) - 用
CHECKSUM TABLE t1也面临同样问题,且 MySQL 8.0.24+ 已弃用该命令,返回值不可信
对比脚本化:用 shell + MySQL client 自动抓差
把 information_schema.tables 查询封装成一行命令,主从结果自动 diff,适合巡检或告警集成:
mysql -h master -u user -p'pass' -Nse "SELECT CONCAT(table_schema,'.',table_name), table_rows FROM information_schema.tables WHERE table_schema NOT IN ('mysql','information_schema','performance_schema','sys') AND table_type='BASE TABLE' ORDER BY 1" > master_rows.txt
mysql -h slave -u user -p'pass' -Nse "SELECT CONCAT(table_schema,'.',table_name), table_rows FROM information_schema.tables WHERE table_schema NOT IN ('mysql','information_schema','performance_schema','sys') AND table_type='BASE TABLE' ORDER BY 1" > slave_rows.txt
diff master_rows.txt slave_rows.txt | grep -E '^[]'
输出类似:
test.users 12887
这就明确告诉你哪张表、差多少行。注意:
- 加
-N去掉列名头,-s禁用表格对齐,确保字段间只用 tab 分隔,diff才能精准比对 - 生产环境建议加超时:
timeout 30 mysql -h ...,防止某张大表元数据卡住整个脚本 - 如果
table_rows全是 NULL,说明该实例未启用innodb_stats_persistent或很久没 ANALYZE,先统一执行ANALYZE TABLE再比
table_rows 看似一致,但某几行字段值已被静默覆盖、或时间戳因时区配置不同而错位的情况——那得靠 checksum 或 pt-table-checksum 的 chunk 级哈希来揪。











