IN和EXISTS性能无固定优劣,取决于表大小、索引、NULL值、数据库版本及实际执行计划;应以执行计划为准,避免盲目替换。
没有固定答案——in和exists谁更快,取决于表大小、索引、null值、数据库版本和实际执行计划,不能一概而论。
看执行计划比背口诀更可靠
Navicat 的「解释计划」(Explain Selected)显示的执行路径,才是真实依据。很多实测发现:IN 和 EXISTS 在现代 Oracle/MySQL 8.0+/PostgreSQL 中,优化器常自动等价重写,最终执行计划完全一致。
- 若执行计划里都走了
NESTED LOOPS或都用了HASH JOIN,性能基本没差别 - 若一个走索引扫描(
INDEX RANGE SCAN),另一个是全表扫描(TABLE ACCESS FULL),那后者必然慢 - Navicat 自带的查询缓存可能干扰多次测试结果,建议每次执行前加
ALTER SYSTEM FLUSH SHARED_POOL(Oracle)或用SELECT SQL_NO_CACHE ...(MySQL)
IN在子查询结果集小时更稳
当内表(子查询表)数据量小(比如几百到几千行),且关联字段有索引时,IN通常更直观、更少出错:
-
IN会先执行子查询,生成确定值列表,主表可高效用索引匹配(如WHERE id IN (1,2,3)直接走id索引) - 子查询无关联条件(如
SELECT x FROM small_table WHERE status = 'A'),IN更易被优化器物化(materialize) - 但若子查询返回含
NULL的列(如SELECT code FROM t2中code允许为NULL),IN逻辑不受影响,仍能正确返回
EXISTS在子查询表大且需关联时更可控
当内表极大(百万级以上),且子查询必须引用外表字段做关联时,EXISTS 更适合表达意图,也更容易命中索引:
-
EXISTS是半连接(semi-join),只要找到第一个匹配就短路退出,避免遍历全部内表数据 - 例如:
SELECT * FROM orders o WHERE EXISTS (SELECT 1 FROM order_items i WHERE i.order_id = o.id AND i.qty > 10)—— 只要每个o.id在order_items里找到一条满足条件的记录即停,不扫全表 - 前提是
order_items(order_id, qty)有联合索引;否则即使写EXISTS,照样慢 - 注意:如果写成
WHERE EXISTS (SELECT 1 FROM t2 WHERE t2.x = o.x) OR o.status = 'draft',OR 会让优化器放弃 unnest,强制 filter,性能暴跌
NOT IN必须替换成NOT EXISTS
这是真正不能妥协的点:只要子查询字段允许为 NULL,NOT IN 就会返回空结果集,且无法走索引。
- 错误示例:
SELECT * FROM users WHERE id NOT IN (SELECT user_id FROM logs)—— 若logs.user_id有NULL,整条语句查不到任何数据 - 正确写法:
SELECT * FROM users u WHERE NOT EXISTS (SELECT 1 FROM logs l WHERE l.user_id = u.id) - 即便
logs.user_id当前没NULL,DDL 定义为NULL也会触发该行为,隐患极隐蔽
真正容易被忽略的不是语法选哪个,而是:不看执行计划直接换写法、忽略字段是否允许 NULL、在 EXISTS 外层混用 OR 条件——这些比 IN 还是 EXISTS 本身危险得多。











