MYSQL · SQL 学习资料

IN vs EXISTS:把 NULL 陷阱看明白

选择 SQL 类型,点击用户行,观察条件如何计算。你还可以开关订单中的 NULL,亲眼看到 NOT IN 与 NOT EXISTS 为什么不一样。

先纠正一个常见误区: IN 的结果集里有 NULL,不代表 IN 永远不为 TRUE。只要找到明确匹配的非 NULL 值,仍然为 TRUE;真正危险的是 NOT IN 在列表含 NULL 时可能无法筛出任何行。NULL IN (NULL) 的结果也不是 TRUE,而是 UNKNOWN。

① users(外表)

点击某一行试试
idname当前 SQL 结果

查询目标:找到存在 / 不存在金额大于 100 的订单的用户。

② orders(内表)

amount > 100
order_iduser_idamount是否满足金额条件

③ SQL 与判断步骤

1. 选中外表用户2. 判断子查询3. 得到 TRUE / FALSE / UNKNOWN

④ 最终查询结果

⑤ 一眼分清:NULL 真值表

IN (1, NULL)
1 IN (1, NULL) → TRUE
2 IN (1, NULL) → UNKNOWN
NULL IN (1, NULL) → UNKNOWN
NOT IN (1, NULL)
1 NOT IN (1, NULL) → FALSE
2 NOT IN (1, NULL) → UNKNOWN
NULL NOT IN (1, NULL) → UNKNOWN
EXISTS (SELECT NULL)
只要子查询返回一行 → TRUE
哪怕这一行的值就是 NULL
因为检查的是“有没有行”
NOT EXISTS (无匹配行)
子查询没有返回行 → TRUE
子查询存在关联匹配行 → FALSE
不会把 NULL 当成相等

⑥ 真实 MySQL 优化器与面试要点

不要机械背“IN 先全部查出来,EXISTS 每行执行一次”。这是便于理解的逻辑模型,不是 MySQL 必然采取的物理执行方案。现代 MySQL 可能对 IN/EXISTS 进行半连接(semijoin)、物化(materialization)、索引查找等优化,两者可能生成相似执行计划。

选择依据:先保证语义正确,再看索引、表大小、选择性和实际执行计划。用 EXPLAIN ANALYZE 检查,不要单纯按“子查询大就选 EXISTS”下结论。

特别注意:上方 EXISTS 的 o.user_id = u.id 遇到 NULL 不会匹配,但这和“EXISTS 完全不受 NULL 影响”是两回事。EXISTS 不检查 SELECT 列的值,但 WHERE 里的 NULL 比较仍服从 SQL 三值逻辑。