left join + is null 能替代 not in 是因为前者保留左表全部行并用 where 右表主键 is null 精准筛出差集,而后者遇右表任意 null 即返回空结果;需确保右表连接字段非空、有索引,且复杂条件须移入 on 子句。

LEFT JOIN + IS NULL 为什么能替代 NOT IN 做差集
因为 LEFT JOIN 会保留左表全部记录,右表无匹配时字段为 NULL;配合 WHERE 右表主键 IS NULL 就能精准筛出“左表有、右表无”的行。这比 NOT IN 更可靠——后者遇到右表任意值为 NULL 时整条查询返回空结果,属于常见静默陷阱。
典型场景:查「注册用户中未下单的用户」或「产品表中未被任何订单引用的 SKU」。
-
NOT IN (SELECT ...)在子查询含NULL时必然失效,且无法利用索引加速 -
LEFT JOIN可走左表驱动 + 右表索引(如右表ON字段建了索引) - 必须用右表的 非空字段 判断
IS NULL,推荐用主键或定义为NOT NULL的外键字段
写法细节:ON 条件和 WHERE 条件不能互换
ON 决定连接逻辑,WHERE 是连接后过滤。把 IS NULL 放在 ON 里会导致语义错误——比如 ON t2.id = t1.ref_id AND t2.id IS NULL 实际是“要求右表匹配一个 id 为 NULL 的记录”,而 NULL 不等于任何值(包括自身),该条件恒假,结果等价于只查左表全量。
正确写法必须分两步:
-
LEFT JOIN t2 ON t1.ref_id = t2.id—— 先完成关联 -
WHERE t2.id IS NULL—— 再筛出未匹配行
若在 WHERE 中混入对左表的额外限制(如 t1.status = 'active'),要放在 WHERE 末尾,否则可能意外过滤掉本应参与连接的左表行。
性能关键:右表连接字段必须有索引
没有索引时,LEFT JOIN 会触发右表全扫描,数据量稍大(比如右表百万级)就明显变慢。即使左表只有几百行,每行都要去右表找匹配,O(n×m) 复杂度直接暴露。
- 检查执行计划:MySQL 用
EXPLAIN,看type是否为ref或eq_ref,key是否显示用了索引 - 索引字段必须和
ON中右侧表达式完全一致,例如ON t1.category_id = t2.id,则应在t2.id上建索引,而非t2.category_id - 复合索引无效:如果右表用的是
(a, b)联合索引,但ON只用了b,索引无法命中
NULL 值处理:IS NULL 比 != 或 更安全
用 t2.id != xxx 或 t2.id xxx 替代 IS NULL 是错的——因为 NULL != 1 返回 UNKNOWN,不进入结果集。而 IS NULL 是唯一能正确识别缺失匹配的语法。
更隐蔽的问题是右表连接字段本身允许 NULL。例如 t2.ref_id 是可空外键,其中部分值为 NULL,此时 LEFT JOIN 后这些行的 t2.ref_id 仍是 NULL,但它们不是“未匹配”,而是“匹配到了 NULL 值”。要排除这类干扰,必须确保右表连接字段定义为 NOT NULL,或在 ON 中加额外约束(如 AND t2.ref_id IS NOT NULL)。
真正容易被忽略的点:差集结果是否要去重?LEFT JOIN 本身不保证去重——如果左表某行在右表有多个重复匹配(比如一对多关系没控制好),它会被排除;但如果右表完全没匹配,左表原样保留,重复行仍存在。需要显式加 DISTINCT 或按业务逻辑聚合。










