必须将多层select from (select from ...)改为显式字段投影,每层只选必要字段(如join键、过滤/排序字段),避免字段膨胀、谓词下推失效及列序错位;相关子查询应改写为join,order by + limit须置于最外层。

把多层 SELECT * FROM (SELECT * FROM ...) 改成显式字段投影
每多一层 SELECT *,数据库就要把所有字段带下去,优化器很难剪掉无用列,内存和IO压力会指数级上升。更麻烦的是,一旦底层表加了新字段,上层视图或嵌套结构可能因列序错位而返回错误数据。
实操建议:
- 每一层只
SELECT下一层真正需要的字段,尤其是 JOIN 键、过滤字段、排序字段 - 给中间结果起明确别名,比如
FROM (SELECT o.id, o.user_id, o.amount FROM orders o) AS o_sub - MySQL 5.7 及以前版本中,
SELECT *在派生表里还会阻止谓词下推,导致 WHERE 白加
用 JOIN 替代 WHERE IN (SELECT ...) 或 EXISTS 相关子查询
相关子查询在 MySQL 5.7 和 SQL Server 中常被当作“N+1 查询”执行:外层每行都触发一次内层扫描。即使内层有索引,整体仍是 O(N×M) 复杂度。
实操建议:
- 把
WHERE customer_id IN (SELECT id FROM customers WHERE region = 'CN')改成JOIN customers c ON o.customer_id = c.id WHERE c.region = 'CN' - 把
EXISTS (SELECT 1 FROM logs l WHERE l.order_id = o.id AND l.status = 'failed')改成LEFT JOIN logs l ON l.order_id = o.id AND l.status = 'failed' WHERE l.order_id IS NOT NULL - 确认改写后执行计划中
type是ref或eq_ref,不是ALL或index
拆掉超过3层的视图嵌套,用物化中间结果替代
视图嵌套超3层时,MySQL 5.7 及以前直接放弃合并(view merging),SQL Server 和 PostgreSQL 的优化器也常失效——它不再尝试估算代价,而是随机选连接顺序,执行计划极易漂移。
实操建议:
- 用
CREATE TEMPORARY TABLE或WITH ... AS MATERIALIZED(PostgreSQL 12+ / MySQL 8.0.23+)把关键中间结果固化下来 - 对临时表建索引,尤其在后续 JOIN 或 WHERE 中高频出现的字段上
- 避免在视图里反复复用相同聚合逻辑,比如
COUNT(*) GROUP BY user_id出现在4层嵌套里,不如先算一次存成临时表
ORDER BY + LIMIT 必须放在最外层,且排序字段要有索引
把 ORDER BY create_time DESC LIMIT 10 塞进子查询,看起来能减少数据量,但多数数据库(MySQL 5.7、SQL Server)无法下推 LIMIT 到物理扫描阶段,反而导致排序上下文丢失、结果错乱,或强制对全量中间结果排序。
实操建议:
- 确保
LIMIT和ORDER BY只出现在最外层SELECT - 为排序字段单独建索引,或作为复合索引的最左前缀;例如
INDEX (status, create_time)支持WHERE status = 'done' ORDER BY create_time DESC - 在 PostgreSQL 中用
EXPLAIN (ANALYZE, BUFFERS)看Sort Method是否用了external merge,若是,说明内存不足,需调大work_mem
嵌套层级本身不是原罪,但每多一层,就多一次优化器放弃控制的机会。真正危险的是那些你看不见的隐式全表扫描、重复计算和字段膨胀——它们藏在 SELECT * 后面,躲在视图定义里,混在 ORDER BY 的位置错误中。










