性能差主因是子查询被内联展开并重复执行,如相关子查询触发嵌套循环导致扫描量暴增,NOT IN/EXISTS易退化为全表扫描,标量子查询逐行执行且难物化,索引失效常见于隐式转换或函数使用。

SELECT * 从含子查询的视图中取数据,性能差不是因为“视图慢”,而是数据库在执行时把子查询原样展开、重复计算——同一段逻辑可能被实例化多次,嵌套越深,开销越指数级放大。
视图展开后子查询被多次实例化
视图只是保存的 SELECT 定义,不缓存结果。当另一个视图或查询引用它时,优化器会把整个子查询逻辑“内联展开”。如果子查询是相关子查询(比如引用了外层字段),就可能触发嵌套循环:外层每行都重跑一遍子查询。
- 例如:
v_user_summary含(SELECT COUNT(*) FROM orders WHERE orders.user_id = users.id);v_active_users又SELECT * FROM v_user_summary WHERE last_login > '2026-01-01' - 最终执行计划里,
COUNT(*)子查询会被展开两次:一次算总订单数,一次只算活跃用户的订单数 - 若外层扫描 10 万用户,子查询平均扫描 500 行订单,则总扫描量达 5000 万行
NOT IN / EXISTS 在视图中退化为嵌套循环
视图定义中用 NOT IN 或 EXISTS,在 MySQL/SQL Server 中几乎必然触发 DEPENDENT SUBQUERY 类型,尤其当子查询条件涉及外层字段时。
-
NOT IN遇到子查询结果含NULL,整条记录被过滤(三值逻辑),且无法利用哈希连接 -
EXISTS若没索引支撑,优化器无法下推谓词,只能走嵌套循环 + 全表扫描 - 错误示例:
WHERE u.id NOT IN (SELECT user_id FROM logins WHERE login_time > NOW() - INTERVAL 30 DAY)——logins表无(user_id, login_time)复合索引时,每次判断都扫全表
标量子查询在 SELECT 列中被逐行执行
把子查询写在 SELECT 列里(如 (SELECT name FROM statuses s WHERE s.id = o.status_id)),语义上是“每行都查一次”,即使结果集只有几十行,也可能变成几百次独立查询。
- 优化器通常不会自动物化这类子查询,尤其在 PostgreSQL 旧版本或 SQL Server 默认配置下
- 更稳做法是提前聚合或关联:
LEFT JOIN statuses s ON o.status_id = s.id,让连接一次性完成 - 注意 NULL 处理:JOIN 后需用
COALESCE(s.name, 'unknown')显式补缺,否则语义可能偏移
为什么加索引也不一定见效
索引失效常发生在子查询展开后的实际执行路径中,而不是单独看子查询本身。
- 先对子查询单独
EXPLAIN,确认它能走索引;再对外层 + 视图整体EXPLAIN,看是否出现DEPENDENT SUBQUERY或Using temporary - 常见坑:
orders.user_id是BIGINT,而子查询里users.id是VARCHAR→ 隐式转换导致索引失效 - 子查询中用了函数:
WHERE YEAR(created_at) = 2024→created_at索引完全失效,必须改写为范围条件
真正卡住性能的,往往不是子查询本身多复杂,而是它被展开后失去独立性、无法复用、又没索引兜底。改写为 JOIN 或派生表,配合复合索引,比调优单个子查询有效得多。


















