LEFT JOIN + IS NULL 是查找左表独有数据的标准写法,通过 LEFT JOIN 关联后用 WHERE 右表主键 IS NULL 筛出未匹配行,需确保 ON 条件完整、字段有联合索引,并注意 NULL 值匹配特性。

LEFT JOIN + IS NULL 是找左表独有数据的标准写法
想从 table_a 中找出那些在 table_b 里完全不存在的记录,核心思路不是“比对值”,而是“确认缺失”——用 LEFT JOIN 拉出所有左表行,再用 WHERE table_b.id IS NULL 筛出右表没匹配上的行。这是最可靠、数据库优化器也最容易走索引的方式。
常见错误是写成 WHERE table_b.id != some_value 或漏掉 IS NULL 判断,这会导致本该被过滤的行仍保留(因为 NULL != anything 结果是 UNKNOWN,不进入 WHERE 真值判断)。
ON 条件必须覆盖完整匹配逻辑
ON 子句决定“什么算匹配上”。如果业务上认为两条记录相等需要同时满足多个字段(比如 user_id + event_time),就必须全写进 ON,不能只靠 WHERE 补:
SELECT a.* FROM table_a a LEFT JOIN table_b b ON a.user_id = b.user_id AND a.event_time = b.event_time WHERE b.user_id IS NULL;
- 把
a.event_time = b.event_time放到WHERE里会先做笛卡尔积再过滤,性能爆炸且语义错误 - 如果
table_b的user_id允许为NULL,而你又用b.user_id IS NULL判断,可能误判——此时应改用b.id IS NULL(假设id是主键/非空唯一标识)
注意 NULL 值在连接中的特殊行为
当 table_a 的关联字段本身含 NULL,比如 a.user_id IS NULL,那么它永远无法和 table_b 中任何一行匹配(因为 NULL = anything 永远为 FALSE),这类行会自动进入结果集——但它们未必是你想要的“业务增量”。
- 如果你只关心非空键的增量,应在
ON前加WHERE a.user_id IS NOT NULL - 如果
table_b的对应字段也有NULL,且你想让NULL和NULL视为相等,需显式写成(a.user_id = b.user_id) OR (a.user_id IS NULL AND b.user_id IS NULL),但多数场景应避免依赖NULL匹配
性能关键:确保关联字段有索引
没有索引时,LEFT JOIN 可能触发全表扫描,尤其当 table_b 很大时,查询会慢得无法接受。
- 在
table_b的ON中涉及的所有字段(如user_id,event_time)上建联合索引,顺序按ON中出现顺序排列 - 不要只给单个字段建索引——
INDEX(user_id)对ON a.user_id = b.user_id AND a.event_time = b.event_time效果有限 - 用
EXPLAIN确认是否走了索引:关注type是否为ref或range,key是否显示索引名
实际增量同步中,最容易被忽略的是时间窗口和去重逻辑——比如同一主键在 table_b 里有多条历史记录,或 table_a 本身存在重复数据,这时候光靠 LEFT JOIN 不够,得先聚合或加 DISTINCT,但那是另一层问题了。










