用left join + is null可高效找出表a有但表b没有的记录,核心是select a.* from table_a a left join table_b b on a.id = b.id where b.id is null;需确保连接字段类型一致、有索引,并用右表主键判null。

用 LEFT JOIN + IS NULL 找出表A有但表B没有的记录
直接对比两个结构相同的表,最常用也最高效的方式是用 LEFT JOIN 配合 IS NULL 判断。前提是两张表有明确的主键或唯一标识列(比如 id 或 order_id),否则无法准确定义“哪一行对应哪一行”。
- 必须在
ON子句中对齐所有用于判断相等的字段,不能只写ON a.id = b.id就完事——如果业务上需要全字段一致才算相同记录,就得把所有关键字段都列出来,例如:ON a.id = b.id AND a.name = b.name AND a.status = b.status - 如果只关心主键差异(比如同步后是否漏了某条ID),那仅比对主键即可,性能更好
- 注意 NULL 值比较:SQL 中
NULL = NULL返回UNKNOWN,不是TRUE,所以不能用a.field = b.field来判断字段级差异,后续会提到替代方案
SELECT a.* FROM table_a a LEFT JOIN table_b b ON a.id = b.id WHERE b.id IS NULL;
用 FULL OUTER JOIN 模拟找出双向差异(MySQL用户需绕行)
FULL OUTER JOIN 理论上能一次性查出“仅在A”“仅在B”“双方都有但字段不同”三类情况,但 MySQL 不支持该语法,必须用 UNION ALL 拼两个 LEFT JOIN。
- PostgreSQL / SQL Server 用户可直接写:
FULL OUTER JOIN ... ON ... WHERE a.id IS NULL OR b.id IS NULL - MySQL 用户得写两段:
LEFT JOIN找 A 有 B 无,再RIGHT JOIN(或反向LEFT JOIN)找 B 有 A 无,最后UNION ALL - 千万别用
UNION(去重),因为你要的就是“重复出现才说明有问题”的逻辑;用UNION ALL保证不丢失原始行数
逐字段比对时,用 COALESCE 或 避开 NULL 判断陷阱
想确认“主键相同但其他字段值不同”的记录?不能简单写 a.name != b.name——当任一字段为 NULL 时,整个表达式结果为 UNKNOWN,被 WHERE 过滤掉。
- MySQL 可用
安全等于操作符:a.name b.name返回1(相等)、0(不等)、1(两者都为 NULL 也算相等) - 其他数据库(如 PostgreSQL、SQL Server)推荐用
COALESCE(a.name, '') != COALESCE(b.name, ''),但要注意默认填充值不能和真实数据冲突(比如用''填充字符串没问题,但用0填充可能误判) - 多字段比对建议拆成多个条件用
OR连接,而不是拼 JSON 或哈希——后者在大数据量下极慢,且无法利用索引
SELECT a.* FROM table_a a INNER JOIN table_b b ON a.id = b.id WHERE a.status b.status AND a.amount b.amount AND NOT (a.name b.name);
大数据量下避免全表扫描:务必给 JOIN 字段加索引
哪怕只是临时对比,只要表超过几万行,没索引的 JOIN 就会变成磁盘 IO 性能灾难。
- 至少确保
ON子句里用到的所有字段,在两张表上都有单列索引或联合索引 - 如果经常做这类对比(比如每日数据核对),建议在
table_b上建覆盖索引,包含所有参与比对的字段,减少回表 -
EXPLAIN一定要看:确认type是ref或eq_ref,而不是ALL;如果出现Using temporary; Using filesort,说明查询结构可能需要调整
实际执行前先估算行数:SELECT COUNT(<em>) FROM table_a</em> 和 SELECT COUNT() FROM table_b,如果相差极大,优先从较小表驱动 JOIN,能显著缩短响应时间。
字段类型不一致也会让索引失效——比如 a.id 是 BIGINT,b.id 是 VARCHAR,即使值看起来一样,JOIN 时也会隐式转换,跳过索引。这种细节容易被忽略,但一旦发生,查询可能从毫秒级变分钟级。











