not in 遇到 null 会因三值逻辑返回 unknown,导致整个查询结果为空;应改用 not exists,其不受 null 影响、语义等价且可走索引。

NOT IN 遇到 NULL 就失效,改用 NOT EXISTS
子查询返回结果里只要有一行是 NULL,NOT IN 整个条件就变成 UNKNOWN,导致外层查询没数据——不是逻辑错,是三值逻辑的必然结果。比如:SELECT * FROM orders WHERE user_id NOT IN (SELECT id FROM users),如果 users.id 有 NULL,哪怕有 100 个孤儿订单也查不出来。
- 一律改用
NOT EXISTS:语义等价、不受NULL影响、还能走索引 - 别手动过滤
WHERE id IS NOT NULL,那只是绕开问题,不是解决逻辑缺陷 - 检查所有子查询字段是否可能为
NULL,尤其是关联字段来自 LEFT JOIN 或可空列
UPDATE/DELETE 里嵌套子查询必须加 LIMIT 或分批
生产环境执行 UPDATE ... WHERE id IN (SELECT ...) 时,MySQL 会先物化整个子查询结果(即生成 DERIVED 表),再做匹配。如果子查询返回百万行,不仅慢,还可能锁表、OOM、拖垮主从同步。
- 写法上必须带
LIMIT,例如WHERE id IN (SELECT id FROM t WHERE ...) LIMIT 1000 - MySQL 5.7 及更早版本不支持
ORDER BY + LIMIT在子查询中用于 DML,会直接报错This version of MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery' - 真正大批量操作,拆成循环:每次取
id BETWEEN ? AND ?或用游标式分页,配合BEGIN/COMMIT
子查询性能崩了?先看 EXPLAIN 里的 DERIVED
执行 EXPLAIN 后如果看到 type = DERIVED,说明 MySQL 把子查询结果临时存成了内存或磁盘表,没有索引、不可复用、无法下推条件——这是嵌套查询最典型的性能雷区。
- 优先考虑改写为
JOIN:尤其当子查询只用于关联或过滤时,JOIN更易优化、能用上外键/索引 - 子查询里避免
GROUP BY+HAVING组合,这类结构极难被优化器下推 - 确认子查询是否真的需要全量结果:比如只判断存在性,就用
EXISTS并在子查询里加LIMIT 1
跨库、隐式转换、字段类型不一致全会出事
嵌套查询看似只是“套一层 SELECT”,但实际执行时,数据库要解析两层上下文:外层字段类型、子查询返回类型、连接方式、权限范围——任何一项不匹配都可能触发静默降级或意外行为。
- 跨库查询(如
db1.t1.id IN (SELECT id FROM db2.t2))在某些 MySQL 版本或配置下会被拒绝,或退化为驱动表扫描 -
user_id VARCHAR(32)对比子查询返回的INT,会触发全表隐式转换,索引直接失效 - 子查询字段别名没起、或用了关键字(如
order、group),外层引用时可能语法报错或语义错乱
真实场景里,嵌套查询不是不能用,而是每多一层,就多一个执行计划不可控点。最危险的不是写错,是看起来跑通了,但上线后某天数据量一涨、某条记录多了个 NULL、某个字段悄悄改了类型——然后凌晨三点告警炸了。










