用 NOT EXISTS 查缺失关联记录最稳,因它天然短路、不惧 NULL;GROUP BY + HAVING 校验聚合一致性;EXCEPT/MINUS 比对快照差异;子查询须精简字段并加索引。

用 NOT EXISTS 查缺失的关联记录最稳
外键约束没建或被绕过时,主表有数据、从表却没对应记录,这种“孤儿数据”靠子查询最容易揪出来。NOT EXISTS 比 LEFT JOIN ... IS NULL 更直观,执行计划也更可控——它天然短路,一找到匹配就停,不扫全表。
常见错误是写成 NOT IN:只要从表关联字段含 NULL,整条结果就变空,校验直接失效。
- 正确写法:
SELECT id FROM orders WHERE NOT EXISTS (SELECT 1 FROM customers WHERE customers.id = orders.customer_id)
- 别用
NOT IN (SELECT customer_id FROM customers),customers.customer_id有NULL就漏检 - 确保子查询里
WHERE条件列有索引,否则大表上会慢到卡住
用 GROUP BY + HAVING 校验聚合逻辑一致性
业务规则常要求“每个订单的明细行总金额必须等于订单头金额”,这类跨层级数值一致性没法靠外键,得靠分组后筛异常组。
关键在 HAVING 里直接写校验表达式,别先算出中间值再 WHERE,否则聚合前过滤会丢数据。
- 示例(查金额对不上的订单):
SELECT order_id, SUM(item_amount) AS detail_sum, header_amount FROM orders o JOIN order_items oi ON o.id = oi.order_id GROUP BY order_id, header_amount HAVING SUM(item_amount) != header_amount
-
header_amount必须进GROUP BY,否则 MySQL 8.0+ 会报错,其他数据库也可能非确定性返回 - 如果
item_amount允许为NULL,SUM()会跳过它,但你可能需要SUM(COALESCE(item_amount, 0))显式处理
用 EXCEPT(或 MINUS)比对两个快照是否一致
上线前验证迁移脚本、定时任务跑完后核对源库和目标库,用集合差集最干净——有差异就出来,没差异就空结果集。
注意不同数据库关键字不同:EXCEPT 是 PostgreSQL / SQL Server / SQLite 的写法,Oracle 用 MINUS,MySQL 8.0+ 才支持 EXCEPT,旧版得用 LEFT JOIN 模拟。
- 标准写法(PostgreSQL):
SELECT id, name, status FROM users_production EXCEPT SELECT id, name, status FROM users_staging
- 字段顺序、类型、数量必须完全一致,否则报错;字符串比较区分大小写,需提前
UPPER()统一 - 大数据量时加
LIMIT 10快速确认有无差异,别直接跑全量
子查询校验的性能雷区:别让 SELECT * 拖垮整个检查
校验逻辑本身不复杂,但子查询里随手写 SELECT * 或没加 LIMIT,在千万级表上可能跑十几分钟,还锁表。
核心原则:子查询只取校验必需的字段,主查询也只选定位用的 ID 或关键标识。
- 错例:
SELECT * FROM products WHERE id IN (SELECT * FROM invalid_skus)
—— 子查询返回 5 列,实际只用第一列 - 正例:
SELECT id FROM products WHERE id IN (SELECT sku_id FROM invalid_skus)
- 如果只是检查“是否存在”,用
EXISTS比IN更轻量,它不关心子查询返回什么,只看有没有行
校验逻辑越贴近业务规则本身,就越难绕过;但每多一层嵌套、每多一个未索引字段,就多一分超时风险。真正卡住人的,往往不是语法,而是没想清楚“我到底要证明什么”和“这张表此刻能不能扛住这个查”。

















