not exists 比 left join is null 更高效且语义更可靠,因其采用半连接、短路扫描、不生成中间结果,而后者需全量关联再过滤,易因右表无索引或含null导致性能劣化和逻辑错误。

直接用 LEFT JOIN + WHERE 右表主键 IS NULL,这是最稳、最易读、数据库也最容易优化的方式。
为什么不能用 NOT EXISTS 或子查询?
能用,但没必要——NOT EXISTS 在多数场景下执行计划更重,尤其当主表很大、右表没索引时,可能触发嵌套循环全扫;而 LEFT JOIN 能更好利用哈希连接或排序合并,且语义直白:我要主表所有行,只要右边没匹配上就留下。
-
NOT EXISTS适合右表有复杂过滤条件(比如带WHERE status = 'active')且无法提前聚合时 - 子查询(如
WHERE id NOT IN (SELECT customer_id FROM orders))在右表含NULL时会彻底失效,必须加AND customer_id IS NOT NULL - 如果只是查“有没有关联”,
LEFT JOIN的意图和性能都更干净
LEFT JOIN 的 ON 和 WHERE 到底怎么写?
关键就一条:关联条件只放 ON,筛选逻辑只放 WHERE。常见错误是把 c.id IS NULL 写进 ON 子句,这会让右表只参与匹配 NULL 行,失去左连接本意。
- ✅ 正确:
LEFT JOIN customers c ON o.customer_id = c.id WHERE c.id IS NULL - ❌ 错误:
LEFT JOIN customers c ON o.customer_id = c.id AND c.id IS NULL - 如果右表要加状态过滤(比如只关心已激活客户),必须拆到
WHERE或用子查询预过滤,否则会漏掉主表中本该算“孤立”的记录
索引没建好,查得再对也慢
大表跑 IS NULL 慢,90% 是因为缺索引。不是“加了索引就行”,而是要对准字段和用途:
- 主表外键字段(如
orders.customer_id)必须有索引,否则LEFT JOIN会全表扫描主表 - 从表主键/被引用字段(如
customers.id)要有主键或唯一索引,这是连接效率的基础 - MySQL 5.7 以前版本对
IS NULL常走不了索引,可临时加OR 1=0强制走(需看EXPLAIN验证) - PostgreSQL 支持函数索引,但
CREATE INDEX ... WHERE id IS NULL实际极少有用,优先保证主外键索引质量
真正上线前最容易忽略的,是把 IS NULL 错塞进 ON 条件,以及外键字段没索引——这两点一出,要么结果为空,要么跑十分钟不出结果。










