首页 / 技术分享 /
MySQL 中 SQL 语句关键字的执行顺序

MySQL 中 SQL 语句关键字的执行顺序

GPT-5

2026-01-03
5 次浏览
0 条评论

本文说明 MySQL 中常见 SQL 关键字的“逻辑处理顺序”(Logical Query Processing Order),并结合优化器行为给出注意事项与示例。

数据库
MySQL
SQL
执行顺序
分享:

请注意:逻辑顺序用于理解语义与可用性(例如某些表达式为何不能出现在 WHERE 中),实际执行由优化器决定,可能重排或重写以获得更高性能。

三种“顺序”概念

  • 书写顺序(Syntactic Order):你写在 SQL 里的顺序,如 SELECT ... FROM ... WHERE ... GROUP BY ... HAVING ... ORDER BY ... LIMIT ...
  • 逻辑处理顺序(Logical Order):SQL 标准定义的结果生成步骤,决定了哪些列/别名在某处可见。
  • 物理执行顺序(Physical Plan):MySQL 优化器根据统计信息与索引生成的实际执行计划,可能与书写顺序不同。

典型 SELECT 的逻辑处理顺序(MySQL 8.0)

  1. WITH(CTE)
  2. FROM(表、子查询、视图、派生表)
  3. JOIN 与 ON(针对每个连接)
  4. WHERE(筛掉不满足条件的行)
  5. GROUP BY(形成分组)
  6. HAVING(基于聚合结果的分组过滤)
  7. 窗口函数(OVER 子句;在 MySQL 8.0 中作为选择列表/ORDER BY 可用的表达式)
  8. SELECT(计算选择列表表达式、列别名)
  9. DISTINCT(去重)
  10. UNION / UNION ALL(合并多 SELECT 的结果;最终 ORDER BY 通常作用于整体)
  11. ORDER BY(排序)
  12. LIMIT/OFFSET(截取行)

说明:

  • 窗口函数不能用于 WHERE/GROUP BY/HAVING,通常在这些阶段之后、ORDER BY/LIMIT 之前(作为 SELECTORDER BY 中的表达式)求值。
  • DISTINCT 在选择列表计算后再去重。
  • ORDER BY 若在子查询中且结果未被外层使用排序,优化器可能移除(Order By elimination)。

各阶段要点

1) WITH(CTE)

  • 在主查询之前生成临时结果集;MySQL 可能将简单 CTE 内联(merge),复杂或被多次引用的 CTE可能被物化。
  • 递归 CTE:先执行锚成员,再迭代递归成员直至不产生新行。

2) FROM

  • 确定参与查询的数据源:基本表、视图、子查询(派生表)。
  • 子查询与视图在此阶段逻辑上“展开”。优化器可能选择物化或内联。

3) JOIN 与 ON

  • 对每个连接应用其 ON 条件。
  • 外连接注意:
    • ON 是连接条件;对于 LEFT/RIGHT OUTER JOIN,ON 过滤不丢弃保留侧(左/右)行。
    • WHERE 在连接之后再过滤,可能把外连接“变窄”甚至退化为内连接(若过滤了保留侧的 NULL 行)。

4) WHERE

  • 行级过滤,不能使用聚合或窗口函数。
  • 允许使用可计算的列与常量表达式;列别名在此不可见(因为别名在 SELECT 才生成)。

5) GROUP BY

  • 基于分组键形成组;组内才能使用聚合函数(COUNT/SUM/AVG/...)。
  • MySQL 中非完全依赖分组键的选择列需使用聚合或基于功能依赖(仅在特定 SQL 模式下可能放宽,建议遵循标准写法)。

6) HAVING

  • 针对分组后的结果过滤,允许使用聚合。
  • 不可使用窗口函数;列别名在此可见(因发生在 SELECT 之前的逻辑争议,MySQL 实现允许在 HAVING 引用选择列表别名,但更稳妥是直接写表达式或聚合)。

7) 窗口函数(OVER)

  • 在 MySQL 8.0 中,窗口函数只能出现在 SELECTORDER BY 表达式中。
  • 逻辑上在分组与 HAVING 之后求值;不能出现在 WHERE/GROUP BY/HAVING
  • 可基于 PARTITION BYORDER BY 定义窗口。

8) SELECT

  • 计算选择列表表达式并生成列别名。
  • 此时可使用聚合或窗口函数的结果。

9) DISTINCT

  • 对选择列表的结果去重。

10) UNION / UNION ALL

  • 每个子 SELECT 按上述顺序独立处理,然后:
    • UNION ALL:连接结果(不去重)。
    • UNION:连接并去重(相当于在合并后应用 DISTINCT)。
  • MySQL 的 ORDER BY/LIMIT 若作用于整个 UNION,需写在 UNION 语句末尾;单个分支的 ORDER BY/LIMIT 仅对该分支有效。

11) ORDER BY

  • 对最终结果排序;
  • 可使用选择列表别名与窗口函数表达式。

12) LIMIT/OFFSET

  • 在排序后截取指定区间的行;若无排序,优化器可能使用索引或其它方式快速提取。

常见语义差异与注意事项

  • 别名可见性:
    • WHERE 阶段不可引用 SELECT 别名;
    • ORDER BY 阶段可以引用 SELECT 别名。
  • 外连接过滤:
    • 将条件写在 ON 与写在 WHERE 的效果,在 OUTER JOIN 下可能不同。WHERE 可能过滤掉填充的 NULL,从而改变连接语义。
  • 聚合与非聚合列:
    • SELECT 中出现非分组列时,必须使用聚合或符合功能依赖(建议总是明确聚合)。
  • 窗口函数:
    • 不能用于 WHERE/GROUP BY/HAVING
    • 常与聚合并列,但语义不同:窗口函数“不折叠行”(保留明细行),聚合“折叠为一行/每组一行”。

示例与解说

示例 1:典型查询

WITH active_customers AS (
  SELECT id, name
  FROM customers
  WHERE status = 'active'
)
SELECT
  c.id,
  c.name,
  SUM(o.amount) AS total_amount,
  ROW_NUMBER() OVER (PARTITION BY c.id ORDER BY o.created_at) AS rn
FROM active_customers AS c
JOIN orders AS o
  ON o.customer_id = c.id
WHERE o.created_at >= '2025-01-01'
GROUP BY c.id, c.name
HAVING SUM(o.amount) > 1000
ORDER BY total_amount DESC
LIMIT 10 OFFSET 0;

逻辑顺序讲解:

  1. 计算 CTE active_customers
  2. FROM/JOINactive_customersorders 连接,ON 应用连接条件;
  3. WHERE 过滤订单时间;
  4. GROUP BY 按客户分组;
  5. HAVING 基于聚合过滤分组;
  6. 计算选择列,包括聚合与窗口函数 ROW_NUMBER()(不能用于 WHERE/HAVING);
  7. ORDER BY 使用别名 total_amount
  8. LIMIT 截取前 10 行。

示例 2:ON vs WHERE(外连接)

-- 将订单表左连到客户表,保留所有客户
SELECT c.id, o.id AS order_id
FROM customers c
LEFT JOIN orders o
  ON o.customer_id = c.id AND o.status = 'paid' -- 在 ON 里筛选订单
WHERE c.region = 'EU';                           -- WHERE 只过滤客户侧
  • 若把 o.status = 'paid' 放到 WHERE,在 LEFT JOIN 下会过滤掉没有订单或未支付订单的客户(因为 o.* 为 NULL,不满足 WHERE),语义改变。

示例 3:窗口函数不能在 WHERE 中

-- 错误:窗口函数在 WHERE 中不允许
SELECT *
FROM t
WHERE ROW_NUMBER() OVER (ORDER BY id) <= 10;

-- 正确:在选择后使用别名或将窗口函数放子查询/CTE
WITH ranked AS (
  SELECT
    t.*,
    ROW_NUMBER() OVER (ORDER BY id) AS rn
  FROM t
)
SELECT *
FROM ranked
WHERE rn <= 10;

UNION 与 ORDER BY/LIMIT

SELECT id, name FROM a
UNION ALL
SELECT id, name FROM b
ORDER BY id
LIMIT 100;
  • 两个分支各自完成逻辑处理后再合并;
  • 末尾的 ORDER BY/LIMIT 作用于合并后的整体结果。

优化器可能进行的重写(不改变语义)

  • 连接重排序(Join Reordering)与连接消除(Join Elimination)
  • 谓词下推(Predicate Pushdown):将 WHERE/HAVING 条件尽量下推到更早阶段或更靠近数据源
  • 子查询去相关化(De-correlation)、半连接(Semi-Join)与物化/内联选择
  • ORDER BY 消除:若排序对外层结果无影响,可能移除
  • 使用索引访问路径、覆盖索引与回表优化

这些优化不改变逻辑语义,但会影响物理顺序与性能。


速查:你该把表达式放在哪里

  • 过滤明细行:WHERE
  • 过滤分组:HAVING
  • 生成明细与聚合列:SELECT
  • 定义分组:GROUP BY
  • 定义连接条件:ON
  • 排序与分页:ORDER BY + LIMIT/OFFSET
  • 窗口统计(排名、累计、同组比较):SELECT/ORDER BY 中的窗口函数

常见误区

  • WHERE 引用选择列表别名或窗口函数(不可行)
  • 把分组过滤写在 WHERE(应写 HAVING
  • 在外连接中将保留侧的过滤条件写到 WHERE,改变了外连接语义
  • 认为书写顺序等于执行顺序;优化器可以重排但不改变结果语义

参考

  • MySQL 8.0 Manual:Queries, SELECT Syntax, Window Functions, WITH (CTE)
  • SQL 标准与通用“Logical Query Processing”顺序
  • 《High Performance MySQL》关于优化器与执行计划的章节

评论区 (0)

你需要先 登录 后才能发表评论。
还没有人评论,赶快成为第一个吧。

关于云信益站

云信益站是由荣县人创办的公益网站,集家乡宣传、技术分享与开发服务于一体。在这里,您可以探索荣县的美景、美食与历史,查询实用本地信息,学习软件开发技术。让我们以数字技术连接桑梓,赋能家乡发展。

联系站长

关注我们

© 2025 云信益站. 保留所有权利.