except 和 not exists 比 left join + is null 更可靠,因后者在字段含 null 时因 null = null 返回 unknown 而漏行;except 是集合差运算,天然去重、忽略顺序,要求列数类型顺序严格一致;mysql 5.7 不支持需用 not exists,后者需关联子查询并显式处理 null。

子查询对比数据差异的核心逻辑
直接用 EXCEPT 或 NOT EXISTS 比 LEFT JOIN + IS NULL 更可靠,尤其当字段含 NULL 时。因为 NULL = NULL 返回 UNKNOWN,导致 JOIN 条件失效,漏掉差异行。
用 EXCEPT 找出 v2 有但 v1 没有的记录
EXCEPT 是集合差运算,天然去重、忽略顺序,适合比对全字段差异。注意它要求两侧列数、类型、顺序严格一致。
- 必须确保两个子查询返回相同数量和类型的列,比如都选
id, name, status,不能一个选*一个选具体字段 - PostgreSQL 和 SQL Server 支持
EXCEPT;MySQL 8.0+ 才支持,5.7 及更早版本需改用NOT EXISTS - 如果想保留重复行(比如某条记录在 v2 中出现 3 次、v1 中 1 次),
EXCEPT ALL可用(PostgreSQL 支持,SQL Server 不支持)
SELECT id, name, updated_at FROM version_2 EXCEPT SELECT id, name, updated_at FROM version_1;
用 NOT EXISTS 处理 MySQL 5.7 或含主键的精确比对
当需要按主键或唯一键逐行比对,或数据库不支持 EXCEPT 时,NOT EXISTS 更可控,且能明确指定匹配条件。
- 写法里必须用相关子查询,即子查询中引用外层表字段,例如
v2.id = v1.id - 所有用于比对的字段都要参与
=判断,NULL字段要单独用IS NULL补充,否则整行被跳过 - 性能上,若
version_1表很大,建议在id和比对字段上建复合索引
SELECT v2.* FROM version_2 v2
WHERE NOT EXISTS (
SELECT 1 FROM version_1 v1
WHERE v1.id = v2.id
AND v1.name = v2.name
AND v1.updated_at = v2.updated_at
AND (v1.status = v2.status OR (v1.status IS NULL AND v2.status IS NULL))
);
别忽略时间戳精度和字符集隐式转换
看似数据一样却查不出差异?大概率是 updated_at 带毫秒但显示截断,或 name 字段在两个版本表中字符集不同(如 utf8mb4 vs utf8),导致比较时隐式转换失败。
- 用
CAST(col AS CHAR)或HEX(col)查看真实值,尤其对TIMESTAMP和VARCHAR - 跨库比对时,先确认两表的
COLLATION是否一致,不一致就显式加COLLATE utf8mb4_0900_as_cs - 如果只关心业务关键字段差异,不要无脑
SELECT *,明确列出字段并检查每项是否允许NULL
实际跑通的关键不在语法多炫,而在搞清哪几列真能代表“同一行”——ID?组合键?还是带时间戳的全字段快照?这一步错了,后面全白算。










