真实对比WHERE IN子查询和等价JOIN写法在同一个存在性判断场景下的开销,用EXPLAIN看清两者底层真实不同的执行路径。
代码-- IN子查询写法 SELECT count() FROM demo.jleft WHERE k IN (SELECT id FROM demo.dictsrc WHERE id < 100000); -- 等价的JOIN写法 SELECT count() FROM demo.jleft INNER JOIN (SELECT id FROM demo.dictsrc WHERE id < 100000) AS sub ON jleft.k=sub.id;
IN子查询:真实内存 4,582,165 字节(约4.58MB),耗时 5ms
JOIN写法:真实内存 6,840,965 字节(约6.84MB),耗时 7ms
EXPLAIN actions=1 ... WHERE k IN (...);
→ CreatingSets (Create sets before main query execution) ← 真实专门的Set结构
└──Aggregating
└──Filter
│ Filter column: k IN subquery1
└──ReadFromMergeTree (demo.jleft)EXPLAIN真实揭示了两者底层机制的差异:IN子查询走的是CreatingSets——ClickHouse会把子查询的结果构建成一个专门为"这个值在不在这个集合里"这种存在性判断优化过的Set数据结构,只需要记住哪些值出现过,不需要保留子查询结果里其他列的信息;而JOIN走的是通用的哈希JOIN机制,即使这次JOIN的唯一目的也只是判断存在与否,构建出来的哈希表依然要按照JOIN的通用语义保留右表的完整行信息(虽然本文这个例子刚好子查询只SELECT了id一列,代价没有被进一步放大)。
每一步都是本文上面用真实两种写法跑出来的原始结果。
EXPLAIN actions=1 对比CreatingSets与哈希JOIN