count(*)统计所有行(含null),count(distinct id)先剔除null再对主键去重计数,二者结果应相等;若不等,说明存在主键null、where漏写或字符集写入失败等问题。

直接比对 COUNT(*) 和 COUNT(DISTINCT 主键) 最快但容易误判
迁移后第一件事不是跑哈希,而是执行 COUNT(*) 和 COUNT(DISTINCT id) —— 这能立刻暴露主键 NULL、WHERE 条件漏写、字符集导致的写入失败等硬伤。
- 必须在相同过滤条件下执行:如果源库导出加了
WHERE created_at >= '2023-01-01',目标库也要查同一范围 - 无主键表不能用
COUNT(DISTINCT id),得换业务唯一字段,比如COUNT(DISTINCT order_no)或组合字段COUNT(DISTINCT user_id, created_at) - 注意 ORM 自动补默认值(如把 NULL 变成 0)、TINYINT(1) 映射为 BOOLEAN 后精度丢失——这些都会让行数一致但内容错位,
COUNT完全无法发现
小表用 CHECKSUM TABLE 快速扫一遍,但浮点字段必须跳过
CHECKSUM TABLE 是 MySQL 内置命令,适合单表 ≤ 500 万行。它算的是数据页级 CRC32,轻量、不用装工具,但有明确限制:
- 遇到
FLOAT或DOUBLE字段,不同 MySQL 版本对二进制表示处理不一致,哪怕值一样校验和也不同——这种表必须跳过,改用MD5(CONCAT_WS('#', col1, col2))拼接哈希 - 命令会加读锁,大表执行期间阻塞查询,务必连从库验证,或安排在低峰期
- 它只校验数据行,不包含索引、AUTO_INCREMENT 值、外键约束,结构差异得另查
SHOW CREATE TABLE
大表必须用 pt-table-checksum,否则根本跑不完
单表超千万行时,CHECKSUM TABLE 和手写 MD5(CONCAT()) 都会内存溢出或锁表太久。pt-table-checksum 的核心价值是分块 + 复制流 + 事务快照,能准确定位到哪几行不一致。
- 启动前确认四件事:
binlog_format = ROW、连接的是主库、校验用户有SELECT/PROCESS/SUPER权限、每张表都有主键或非空唯一索引 - 目标库
Seconds_Behind_Master应尽量接近 0,SQL_THREAD 不能停 - 最小安全命令示例:
pt-table-checksum --host=master_ip --user=check_user --password=xxx --databases=mydb --replicate=percona.checksums --chunk-size=1000 --ignore-databases="information_schema,mysql"
业务字段要单独抽样比对,尤其是金额、JSON、TEXT 类型
技术层校验通过 ≠ 数据可用。金额字段可能因精度映射少小数位,JSON 字段因空格/转义顺序不同导致哈希不等,TEXT 字段可能被截断——这些都得人工介入。
- 对订单表,抽样
SELECT order_no, amount, pay_time, extra_info FROM orders WHERE id % 1000 = 0,两边导出后用sort + diff比较(注意 NULL 处理、时区、字符集隐式转换) - 金额类字段必须用
SUM(amount)和MIN/MAX(amount)聚合比对,不能只看哈希 - JSON 字段建议用
JSON_CONTAINS或JSON_EXTRACT抽关键路径比对,避免整字段哈希失效











