DEPENDENT SUBQUERY在视图中每次调用都会原样展开并重复执行,外层10万行即触发10万次子查询,导致性能急剧下降;NOT IN需改写为LEFT JOIN + IS NULL以规避NULL值导致结果为空的三值逻辑问题。

视图里的子查询不会被缓存或物化,每次调用都会原样展开,性能问题直接暴露在执行计划里——这不是“写得不够优雅”的问题,而是数据库引擎根本没法帮你省事。
DEPENDENT SUBQUERY 在 EXPLAIN 里反复出现就是危险信号
只要视图定义里有引用外层字段的子查询(比如 WHERE u.id = o.user_id),MySQL/PostgreSQL/SQL Server 都会标记为 DEPENDENT SUBQUERY。这意味着:外层结果每返回一行,子查询就重跑一遍。
- 外层查出 10 万行 → 子查询执行 10 万次
- 哪怕子查询只扫 10 行,总扫描量也是 100 万行;若没索引,可能变成 10 万 × 全表扫描
-
EXPLAIN中看到loops=100000且rows很小,基本可以确认是这个模式
NOT IN 必须改 LEFT JOIN + IS NULL,否则 NULL 值直接吞掉全部结果
视图中写 NOT IN (SELECT ...) 是高危操作:只要子查询结果里有一个 NULL,整个条件恒为 UNKNOWN,结果集为空。这不是 bug,是 SQL 三值逻辑的必然行为。
- 正确改法是把子查询逻辑挪进
ON:例如LEFT JOIN user_logins l ON u.user_id = l.user_id AND l.login_time > ... -
WHERE l.user_id IS NULL才能准确表达“没匹配到”语义 - 漏掉
AND条件写在WHERE里,会退化成INNER JOIN,结果错得更隐蔽
EXISTS 或标量子查询要提前聚合,别让 JOIN 跑出笛卡尔积
想查“每个用户是否有已发货订单”,直接 (SELECT 1 FROM order_items WHERE ...) 是典型陷阱。优化不是简单套 LEFT JOIN order_items,而是先压缩维度:
- 先用子查询聚合:
(SELECT order_id FROM order_items WHERE status = 'shipped' GROUP BY order_id) - 或用窗口函数:
ROW_NUMBER() OVER (PARTITION BY order_id ORDER BY shipped_at DESC) - 否则
JOIN后一行用户变多行订单,再DISTINCT或GROUP BY,去重成本可能比原子查询还高
复合索引缺失会让所有改写失效
把子查询改成 LEFT JOIN 后性能没提升?大概率是缺索引。关键不是单字段索引,而是复合顺序必须匹配 ON 条件和过滤条件:
- 例如
LEFT JOIN user_logins l ON u.user_id = l.user_id AND l.login_time > ...,索引必须是(user_id, login_time),反过来无效 -
EXPLAIN中key列显示NULL或只用了第一个字段,说明索引没生效 - 统计信息过期也会误导优化器选错连接方式,定期
ANALYZE TABLE(PostgreSQL)或UPDATE STATISTICS(SQL Server)不能跳过
最常被忽略的一点:视图本身不保存执行计划,它只是 SQL 文本的别名。你改了视图定义,但上游应用如果缓存了旧的执行计划(比如 SQL Server 的 plan cache),或者嵌套在更外层的视图里被多次展开,问题依然存在。上线前务必用真实数据量跑 EXPLAIN ANALYZE 看实际 actual time 和 loops。










