SQL 中 SELECT 的真正执行顺序解析

8次阅读

select语句逻辑执行顺序为:FROM→JOIN→WHERE→GROUP BY→HAVING→SELECT→ORDER BY→LIMIT;别名在SELECT阶段才生成,故WHERE中不可用;非聚合列须出现在GROUP BY中;HAVING过滤分组后结果;ORDER BY作用于最终投影列。

SQL 中 SELECT 的真正执行顺序解析

SELECT 语句的逻辑执行顺序不是从左到右

很多人写 SELECT a, b FROM t WHERE c > 10 ORDER BY a,就默认 sql 是按这个书写顺序执行的——这是错的。实际数据库引擎先处理 FROMJOIN 找出数据源,再用 WHERE 过滤行,然后才计算 SELECT 列表里的表达式(包括别名、函数、聚合等)。这意味着你不能在 WHERE 中直接引用 SELECT 里定义的别名,比如:SELECT price * 1.1 AS final_price FROM orders WHERE final_price > 100 会报错,因为 WHERE 阶段 final_price 还不存在。

GROUP BY 必须早于 SELECT,但晚于 WHERE

GROUP BY 是对 WHERE 过滤后的结果分组,再把每组压缩成一行;而 SELECT 中出现的非聚合列,必须出现在 GROUP BY 子句中(否则多数数据库如 postgresql、SQL Server 会直接报错,mysql严格模式下也一样)。常见错误是写:SELECT user_id, count(*) FROM logs WHERE status = 'ok' SELECT user_id, COUNT(*) FROM logs WHERE status = 'ok' ——漏了 GROUP BY user_id,或者更隐蔽地,在 SELECT 里加了个 MAX(created_at) 却没意识到它依赖分组后计算。

  • WHERE 过滤的是原始行,HAVING 过滤的是分组后的聚合结果
  • GROUP BY 字段可以是表达式(如 YEAR(order_date)),但该表达式必须和 SELECT 中的非聚合项一致或可推导
  • MySQL 5.7+ 默认开启 sql_mode=ONLY_FULL_GROUP_BY,不满足规则会直接报错,不是静默取任意值

ORDER BY 和 LIMIT 的位置影响性能与语义

ORDER BY 发生在 SELECT 计算之后、LIMIT 之前。这意味着排序对象是最终投影出的列(含别名、函数结果),所以 ORDER BY final_price 是合法的;但如果你在 WHEREGROUP BY 阶段需要排序字段的索引加速,就得确保对应原始列有索引,而不是只给别名建索引(别名根本不会存进索引)。

  • LIMIT(或 TOP / FETCH FIRST)是最晚执行的,但它会提前终止扫描——如果加了 ORDER BY,数据库通常得先排序全部结果再截断;若没 ORDER BY,可能随机返回几行
  • OFFSET 的分页(如 LIMIT 10 OFFSET 1000)在大数据集上性能差,因为仍要跳过前 1000 行,不是“跳过索引位置”
  • PostgreSQL 支持 TABLESAMPLE,MySQL 8.0+ 支持窗口函数配合 ROW_NUMBER() 实现更可控的分页,但本质仍是先生成完整序号再过滤

子查询和 CTE 的执行时机容易被误判

相关子查询(correlated subquery)会在外部查询的每一行上重复执行,比如:SELECT name, (SELECT COUNT(*) FROM orders o WHERE o.user_id = u.id) FROM users u,这里内层查询依赖外层的 u.id,无法提前物化。而 CTE(WITH 子句)在 PostgreSQL 和 SQL Server 中默认是“优化器可内联”的,不一定真生成临时结果集;只有显式加上 MATERIALIZED(PostgreSQL 12+)或用视图包装,才强制物化。MySQL 8.0 的 CTE 默认不物化,但递归 CTE 必须物化。

  • EXPLaiN 看执行计划时,注意子查询是否显示为 DEPENDENT SUBQUERY,这代表性能风险
  • 把相关子查询改写成 LEFT JOIN + 聚合,往往能显著提速
  • CTE 名字只是语法糖,别以为它天然缓存结果——除非数据库明确支持物化且你启用了它

真正麻烦的不是记顺序,而是当优化器重排执行计划(比如下推谓词、提前聚合、索引跳扫)时,逻辑顺序和物理执行路径不再一一对应。这时候看 EXPLAIN ANALYZE 比背口诀管用得多。

text=ZqhQzanResources