MySQL 5.7及更早版本视图中子查询导致外层WHERE无法下推,因视图展开为文本替换而非逻辑重写,相关子查询触发逐行执行(DEPENDENT SUBQUERY),造成全表扫描和性能急剧下降。

视图里用子查询关联,外层WHERE根本下推不了
MySQL(尤其5.7及更早)展开视图时是文本替换,不重写逻辑。一旦视图定义里含子查询(尤其是相关子查询或带HAVING的聚合子查询),优化器就很难把外层WHERE user_id = 123这种条件“下推”到子查询内部去剪枝。结果就是:先执行完整子查询(可能扫全表),再拿结果和外层做匹配——哪怕你只想要1行。
IN子查询在MySQL中默认走DEPENDENT SUBQUERY或SUBQUERY模式
比如SELECT * FROM orders WHERE order_id IN (SELECT order_id FROM refunds),若refunds.order_id没索引,或子查询本身过滤性差,MySQL会为每行orders执行一次子查询(标量行为),或者先物化子查询结果再嵌套循环,两种路径都容易触发type: ALL和Extra: Using where。EXPLAIN里看到select_type: DEPENDENT SUBQUERY基本就是性能红灯。
- 子查询返回结果集过大(如>1000行),MySQL可能转为
HASH JOIN,但若驱动表无索引支撑,仍会TABLE ACCESS FULL -
IN右侧子查询字段类型与主表不一致(比如INTvsVARCHAR),隐式转换直接让索引失效 - MySQL 8.0+虽支持
MERGE提示,但对含GROUP BY/HAVING的子查询,优化器常主动禁用该策略
Oracle里子查询阻断谓词推入和分区裁剪
Oracle执行计划中若出现大量VIEW节点,且PREDICATE列显示的是filter而非access,说明外层条件没落到基表扫描上。更隐蔽的是分区表场景:子查询里只要对分区键用了函数(如SUBSTR(dt, 1, 6)),Oracle就无法识别分区范围,PARTITION RANGE ALL只是假象,实际I/O仍是全分区扫描。
- 子查询中
ROWNUM或ORDER BY会强制物化中间结果,彻底关闭谓词下推通道 - 多层嵌套(>2层)会让Oracle优化器放弃准确估算基数,直接选最保守的全表路径
- 用
DBMS_XPLAN.DISPLAY看Other Tag字段,若含not inlined,就是子查询被隔离执行的明确信号
标量子查询在百万级数据上天然O(N×M)复杂度
像SELECT u.name, (SELECT COUNT(*) FROM orders o WHERE o.user_id = u.id) cnt FROM users u这类写法,数据库不会预聚合,而是对users每行都完整执行一遍子查询。100万用户 × 每次查订单表平均耗时5ms = 5000秒,不是夸张——这是嵌套循环的硬伤,跟索引有没有关系不大。
- 即使
orders(user_id)有索引,也只是加速单次查找,调用次数本身降不下来 - 改用
LEFT JOIN聚合必须小心:COALESCE()补零、空组COUNT和SUM语义差异、多匹配行导致笛卡尔积,都是静默错误源 - 真正等价且可控的替代是CTE物化 + 显式
JOIN,但需确保CTE结果集能走索引关联
视图里的子查询不是语法错误,而是执行模型的“黑盒开关”——它让优化器失去对数据流动的掌控权。最容易被忽略的点是:你以为加了索引就安全了,其实索引只管“怎么找”,而子查询决定的是“找多少次”。

















