left join + is null 是查左表独有记录的唯一可靠方式,本质是筛选右表无匹配的行;not in 或子查询在右表含 null 时因三值逻辑失效,而该方式可规避且优化器支持良好。

LEFT JOIN + IS NULL 是唯一可靠方式
用 LEFT JOIN 查左表独有记录,本质是找“右表没匹配上的行”。很多人误用 NOT IN 或子查询,但它们在右表字段含 NULL 时会返回空结果——因为 NULL NOT IN (1, 2, NULL) 永远为 UNKNOWN,被当作 FALSE 过滤掉。只有 LEFT JOIN ... ON ... WHERE right_table.id IS NULL 能绕过三值逻辑陷阱,且数据库优化器对这种模式识别成熟,执行计划通常走索引嵌套循环。
实操要点:
- ON 条件必须写在
JOIN子句里,不能挪到WHERE(否则退化成INNER JOIN) - 判断右表字段是否为
NULL,优先选主键或非空约束列(如orders.user_id IS NULL),避免用可能为NULL的业务字段 - 确保右表关联字段有索引,否则
JOIN过程会全表扫描右表
别用 NOT EXISTS 替代,除非有特殊理由
NOT EXISTS 语义等价、性能相近,但可读性差且易出错:有人会写成 NOT EXISTS (SELECT 1 FROM right_table WHERE right_table.id = left_table.id),看起来没问题,但如果子查询里漏了关联条件(比如写成 WHERE right_table.id = 123),就会变成恒真/恒假,查出全表或空集。而 LEFT JOIN 的连接关系在语法层面强制绑定,更难写错。
使用场景差异:
- 右表关联条件复杂(如多字段组合、函数表达式)时,
NOT EXISTS可能比LEFT JOIN更易写清楚 - 右表数据量极小(NOT EXISTS 可能略快(优化器倾向用物化)
- 其他情况统一用
LEFT JOIN ... IS NULL,兼容性最好,MySQL/PostgreSQL/SQL Server 都无坑
WHERE 条件要放在 JOIN 前还是后?
所有对左表的过滤(如 WHERE users.status = 'active')必须放在 JOIN 之后的 WHERE 子句;如果提前写在 ON 里(如 ON orders.user_id = users.id AND users.status = 'active'),会导致左表被“提前裁剪”,查出来的孤立记录不完整——比如某个用户状态是 inactive,他没订单,但你仍想看到他作为孤立记录,这时放 ON 里就漏掉了。
正确顺序示例:
SELECT u.* FROM users u LEFT JOIN orders o ON o.user_id = u.id WHERE u.status = 'active' -- ✅ 对左表过滤放这里 AND o.id IS NULL -- ✅ 孤立记录判定也放这里
大数据量下性能卡点在哪?
瓶颈几乎总在右表关联字段缺失索引。例如查 users 中没下单的用户,若 orders.user_id 没索引,MySQL 会为每个用户扫描全量 orders 表。另外两个隐性坑:LEFT JOIN 后若右表有多条匹配记录,会生成笛卡尔积,导致结果行数暴增(虽然 IS NULL 后只剩左表单行,但中间过程已膨胀);还有字符集不一致(如左表 utf8mb4,右表 latin1)会让索引失效,JOIN 变成全表比对。
排查建议:
- 用
EXPLAIN看type是否为ref或eq_ref,不是就说明没走索引 - 检查
orders.user_id和users.id的字符集、排序规则、数据类型是否完全一致 - 如果右表实在无法加索引,考虑先用
SELECT DISTINCT user_id FROM orders物化成临时表再 JOIN
实际写的时候,最容易被忽略的是 ON 和 WHERE 的职责边界——前者只管“怎么连”,后者才管“连完怎么筛”。连错了,筛得再细也没用。










