子查询会切断统计信息传递路径,导致优化器无法下推谓词或反向映射分布信息,只能依赖默认选择率估算基数;多层嵌套使误差指数级放大,引发内存误配、低效连接和参数嗅探失效等问题。

子查询切断统计信息传递路径
优化器依赖原始表的统计信息(如列直方图、行数、选择性)来估算中间结果大小。但一旦出现子查询,尤其是含 GROUP BY、TOP、标量函数或聚合的子查询,优化器就无法把外层谓词下推到子查询内部,也无法将子查询输出列的分布信息反向映射回原表。例如:WHERE u.id IN (SELECT user_id FROM logins WHERE login_time > GETDATE() - 1),优化器看不到 logins 表中 login_time 的实际分布,只能按默认选择率(如 10%)硬猜子查询返回多少 user_id。
多层嵌套放大基数误判误差
每嵌套一层子查询,优化器对最终结果集大小的估算偏差就可能指数级放大。比如第一层子查询被高估 2 倍,第二层再乘以 2 倍,第三层又乘一次——三层嵌套后预估行数可能是真实值的 8 倍。这直接导致:
-
Hash Match或Sort算子申请内存(granted_memory_kb)远超实际需要 - 优化器误判为“小结果集”,强行选哈希连接而非嵌套循环
- 执行计划中出现
Table Spool节点,且其Estimated Number of Rows比实际大几个数量级
参数嗅探在子查询中经常失效
SQL Server 的参数嗅探机制依赖首次执行时的参数值生成并缓存计划。但子查询(特别是相关子查询)会让优化器难以绑定参数与内部谓词的语义关系,导致后续不同参数值复用同一个低效计划。典型表现是:
- 第一次查
status = 'active'得到 100 行,生成了索引查找计划 - 第二次查
status = 'all'实际返回 50 万行,却仍复用原计划,引发大量 I/O 和 TempDB spill -
EXISTS或IN子查询中引用外部参数时,优化器常放弃嗅探而用平均基数估算
MySQL 半连接优化不是万能的
MySQL 5.6+ 引入半连接(semi-join)优化,能把部分 IN/EXISTS 自动转为 JOIN,但这只适用于简单等值条件且子查询可物化的情形。以下情况它基本不生效:
- 子查询里有
ORDER BY+LIMIT(如找每个用户最近一条订单) - 子查询含非确定性函数(
NOW()、RAND()) - 子查询被用作派生表且外层有复杂
WHERE条件,优化器放弃合并而走物化 -
EXPLAIN FORMAT=JSON中看到"materialized_from_subquery": true,说明已降级为临时表,但没索引,后续 JOIN 仍慢
真正难处理的是那些既不能重写为 JOIN、又必须保留逻辑优先级的嵌套结构——这时候不是优化器不够聪明,而是你给它的信息太模糊。










