对账本质是比对,不是查数据:核心是确认两套数据在关键字段上是否完全一致;需用JOIN、UNION ALL和聚合逻辑实现,避免肉眼比对、维度错误、精度不统一、NULL误判等常见问题。

对账本质是比对,不是查数据
财务对账的核心动作只有一个:确认两套来源的数据在关键字段(如金额、单号、日期)上是否完全一致。MySQL 本身不提供“对账函数”,得靠 JOIN、UNION ALL 和聚合逻辑组合实现。常见错误是直接用 SELECT * 查两边再人工肉眼比——一旦记录超几千条,漏错率飙升。
- 必须先明确对账维度:按
order_id?按trade_no+settle_date?维度错了,结果全废 - 金额字段务必统一精度和符号:比如一方是
DECIMAL(12,2),另一方是INT存“分”,不转换直接比会全错 - 空值(
NULL)不能用=判断,得用IS NULL或安全等于操作符
用 LEFT JOIN + IS NULL 找「只有一边有」的单据
这是最常用也最容易写错的场景:银行流水表 bank_log 和内部记账表 accounting_record 对不上,要快速定位缺失项。别用子查询套 NOT IN,它遇到 NULL 就整个返回空集。
SELECT b.trade_no, b.amount, 'bank_only' AS source FROM bank_log b LEFT JOIN accounting_record a ON b.trade_no = a.trade_no WHERE a.trade_no IS NULL;
-
LEFT JOIN保证左边全量,右边匹配不到就为NULL - WHERE 条件必须写在
JOIN之后,写在ON里会变成条件连接,逻辑完全不同 - 如果两边都有可能缺,就补一个反向查询:
RIGHT JOIN或交换表顺序再跑一次
用 GROUP BY + HAVING 找「金额不一致」的单据
两边都有记录,但金额对不上,说明业务逻辑或同步过程出问题。不能靠 WHERE a.amount != b.amount,因为可能一方有多笔拆分入账,得先按单据聚合再比总和。
SELECT trade_no,
SUM(CASE WHEN src='bank' THEN amount ELSE 0 END) AS bank_total,
SUM(CASE WHEN src='acct' THEN amount ELSE 0 END) AS acct_total
FROM (
SELECT trade_no, amount, 'bank' AS src FROM bank_log
UNION ALL
SELECT trade_no, amount, 'acct' AS src FROM accounting_record
) t
GROUP BY trade_no
HAVING bank_total != acct_total;
-
UNION ALL比UNION快,去重由后续GROUP BY控制 -
HAVING是唯一能过滤聚合结果的地方,WHERE在聚合前执行,无效 - 注意时区和日期范围:如果没加
WHERE create_time >= '2024-01-01',历史脏数据会干扰结果
跨库对账时权限和网络延迟是隐形瓶颈
生产环境常把银行表放从库、记账表放主库,甚至分属不同实例。这时 JOIN 直接失效,得换思路。别硬连,先用 SELECT ... INTO OUTFILE 导出关键字段 CSV,再用 Python/Shell 做差集——MySQL 不是万能管道。
- 导出时加
ORDER BY trade_no,方便后续用diff工具比对 - 从库延迟超过 5 秒就暂停对账任务,否则永远在追“假差异”
- 账号权限要最小化:
SELECT只给具体字段,禁用FILE权限除非真需要导出
真正难的不是写 SQL,而是确认哪条记录该进哪张表、谁负责修复、什么时候算对平。SQL 只是把问题显性化,剩下的得靠流程兜住。


















