WHERE IN 与 EXISTS 的选择
今天重构了一段代码,遇到一个典型的场景:用 WHERE IN 还是 EXISTS?
场景
有一个缺陷记录表 A,需要筛选出满足某种工艺条件的记录。工艺信息存储在关联表 B 中。工艺只有3种类型。
原始实现(WHERE IN)
-- 第一步:查 B 表,拿到所有匹配的 ID
SELECT id FROM B WHERE process_name IN ('X', 'Y');
-- 结果可能几千个 ID
-- 第二步:把几千个 ID 塞进 IN
SELECT * FROM A WHERE b_id IN (id1, id2, ..., id3000);
问题在哪?
- 两个 SQL 往返
- 几千个 ID 从数据库传到 Java 内存,再传回去
- IN 列表爆炸,SQL 包体巨大,解析慢
- 数据量继续增长时性能断崖式下跌
- A表可能压根就不需要where in 那么多条数据,如果有分页的话,它可能只需要查10条数据
重构后(EXISTS)
SELECT * FROM A WHERE EXISTS (
SELECT 1 FROM B
WHERE B.id = A.b_id
AND B.process_name IN ('X', 'Y')
);
好处:
- 单次查询,零数据传输
- 数据库内部优化为 semi-join,走索引
- 数据量越大优势越明显
什么场景用 IN?
IN 适合小集合、主表查询。比如根据用户选择的几个状态筛选订单、按几个固定值过滤。列表数量可控(几十个以内),IN 更直观,可读性好。
主表也有过滤条件时呢?
实际场景中主表往往不止关联表过滤,它自己也有筛选条件。EXISTS 天然和主表条件协同:
SELECT * FROM A
WHERE A.warehouse_code = 'WH01' -- 主表过滤
AND A.created_at >= '2024-01-01' -- 主表过滤
AND A.b_id IN (id1, id2, ..., id3000); -- 管你主表筛剩几条,先把 B 的 ID 全查出来再说
IN 的做法:先执行子查询查出所有 ID,再应用主表条件。
SELECT * FROM A
WHERE A.warehouse_code = 'WH01' -- 主表过滤
AND A.created_at >= '2024-01-01' -- 主表过滤
AND EXISTS (
SELECT 1 FROM B
WHERE B.id = A.b_id
AND B.process_name IN ('X', 'Y')
);
EXISTS 的做法:先应用主表条件缩小数据范围,再对缩小后的每一行检查 EXISTS。
优化器会先走 warehouse_code 和 created_at 的索引,只留下满足条件的少量行,然后对每一行执行 EXISTS 子查询(走关联索引)。主表条件越精确,EXISTS 优势越大。而 IN 不管主表筛得多干净,都得先把子查询全量结果集传给应用层再传回来。
实际项目中这个查询还带了仓库、时间范围、产线、次品原因等多个筛选条件,用 EXISTS 把 B 表关联过滤"打包"进主查询,是更合理的选择。
什么场景用 EXISTS?
EXISTS 适合关联子查询、大集合过滤。当 IN 列表可能膨胀到成百上千,甚至需要先查另一张表才能知道哪些值符合条件时,EXISTS 把过滤逻辑下推到数据库内部,避免了业务层的中间数据传输。
一句话
IN 适合你手上已经有一小撮值,"我就查这几个"。EXISTS 适合你只知道条件,"去关联表里查符合条件的所有记录"。