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