数据不一致需分层定位:先排除SQL或环境导致的假异常,再按单库、主从、跨库三类场景锁定范围,接着用工具或SQL比对数据,最后通过binlog、事务和应用日志追溯变更源头。

数据不一致不是“有没有”的问题,而是“在哪、为什么、怎么修”的问题。核心是分层定位:先确认现象是否真由数据库引起,再锁定范围(单表?主从?跨库?),最后追溯变更源头。
一、先排除“假异常”:SQL或环境导致的错觉
很多所谓“不一致”,其实是查询本身有偏差:
- 检查字段是否为 NULL —— 用 = NULL 或 != NULL 永远不成立,必须改用 IS NULL / IS NOT NULL
- 确认字符集与排序规则是否一致,比如 utf8mb4_unicode_ci 和 utf8mb4_bin 对大小写、重音处理不同,影响 LIKE、ORDER BY 甚至 JOIN 结果
- 时间字段是否存的是字符串?NOW() 与存储值比较前,务必确认时区设置(time_zone)和字段类型(DATETIME vs TIMESTAMP)
- ORM 框架可能自动注入条件或覆盖排序,查 general_log 或框架日志,拿到真实执行的 SQL
二、快速定位不一致发生在哪一层
根据架构分三类场景判断:
- 单库内不一致:比如账户余额与流水对不上 → 查外键缺失、事务未回滚、应用漏更新。执行 SHOW CREATE TABLE 看主键/唯一索引是否存在,SELECT * FROM 表 WHERE 主键 = X 直接核验原始记录
- 主从间不一致:读从库发现数据旧或缺失 → 首先运行 SHOW SLAVE STATUS\G,重点看:Slave_IO_Running、Slave_SQL_Running 是否为 Yes,Last_Error 是否有报错,Seconds_Behind_Master 是否持续增长
- 跨服务/跨库不一致:如订单库与库存库状态不符 → 不是数据库问题,而是分布式事务未兜底。需比对业务日志、消息队列投递记录、补偿任务执行情况
三、用工具+SQL快速比对数据内容
不要靠肉眼扫表,用可复现的方法验证差异:
- 小表或关键字段:在主从分别执行 SELECT COUNT(*), SUM(id), MD5(GROUP_CONCAT(id ORDER BY id)) FROM tbl,对比三项结果。注意 GROUP_CONCAT 默认长度 1024,大数据量需提前设大:SET SESSION group_concat_max_len = 1000000
- 中大型表:用 Percona Toolkit 的 pt-table-checksum 在主库运行,自动在从库校验并标出不一致的表;确认后用 pt-table-sync --print 生成修复语句,人工审核后再执行
- 无法装工具时:导出主从关键表为 CSV,用 diff 或 Python 脚本按主键逐行比对(建议用 JSON 格式化每行再 MD5,避免字段顺序干扰)
四、追根溯源:查日志,找谁动了数据
数据变了,一定有人或程序写了它:
- 开 binlog:用 mysqlbinlog --start-datetime="2025-12-22 09:00:00" --base64-output=DECODE-ROWS -v mysql-bin.000001 查指定时间段的 DML 操作,确认是否有误删、全表更新、非幂等插入
- 查活跃事务:SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX 看是否有长事务未提交,阻塞其他操作或造成幻读
- 翻应用日志:匹配时间点,看是否有重复提交、重试逻辑缺陷、手动执行脚本等行为;特别注意带 UUID()、NOW() 的语句——在 STATEMENT 复制模式下极易导致主从值不同










