基于本文回答
0
评论

MySQL中SELECT语句的执行顺序

知识点图片

本文讲解MySQL SELECT语句的逻辑执行顺序,区别于书写顺序。它从FROM开始,经WHERE过滤行,GROUP BY分组,HAVING过滤组,再到SELECT选列和ORDER BY排序,最后由LIMIT截取。

这是一个非常核心且重要的MySQL知识点。理解SELECT语句的执行顺序,对于编写高效、正确的SQL查询至关重要。

我们书写SQL的顺序和MySQL实际执行的顺序是不一样的

书写顺序

我们通常是按照这个顺序来写一个完整的SELECT查询:

sql
SELECT [DISTINCT] select_list
FROM table_references
[JOIN table_references ON join_condition]
WHERE where_condition
GROUP BY group_by_list
HAVING having_condition
ORDER BY order_by_list
LIMIT offset, row_count;

逻辑执行顺序(重点)

MySQL(以及大多数关系型数据库)会按照下面的逻辑顺序来解析和执行这个查询。你可以把它想象成一个数据处理的流水线,数据一步步地被筛选和加工。

  1. FROM:确定数据来源。

    • 执行的第一步是找到查询的表。如果涉及到 JOIN,会根据 ON 条件将多个表的数据连接起来,形成一个庞大的“虚拟表”(Virtual Table)。这个虚拟表包含了所有原始表符合连接条件的行和列。
  2. WHERE:行级过滤。

    • 在第一步生成的虚拟表上,应用 WHERE 子句中的条件。它逐行检查,只保留那些让 WHERE 条件为 TRUE 的行。
    • 关键点WHERE 在数据分组(GROUP BY)之前执行,所以你不能WHERE 子句中使用聚合函数(如 COUNT(), SUM(), AVG())。
  3. GROUP BY:数据分组。

    • 将通过 WHERE 筛选后的结果集,按照 GROUP BY 子句指定的列进行分组。具有相同值的行会被分到同一个组里。
    • 执行完这一步后,后续操作的对象就不再是单条记录,而是整个“分组”了。
  4. HAVING:分组过滤。

    • GROUP BY 之后形成的分组进行过滤。只有满足 HAVING 子句中条件的分组才会被保留下来。
    • 关键点HAVING 在数据分组之后执行,所以你可以HAVING 子句中使用聚合函数。这正是它与 WHERE 的核心区别。
  5. SELECT:选择列/计算表达式。

    • 到了这一步,MySQL才真正开始处理 SELECT 后面跟着的列了。它会从保留下来的分组数据中,选出你指定的列,或者计算 SELECT 列表中的表达式(比如 CONCAT(), AVG(score)等)。
    • 关键点:因为 SELECTGROUP BY 之后执行,所以 SELECT 列表中既可以包含分组列,也可以包含聚合函数。同时,为列指定的别名(Alias)也是在这一步生成的。
  6. DISTINCT:去除重复行。

    • 如果查询中包含了 DISTINCT 关键字,那么在 SELECT 选出所有列之后,会移除结果集中完全重复的行。
  7. ORDER BY:排序。

    • 对最终的结果集按照 ORDER BY 子句指定的列进行排序。
    • 关键点:因为 ORDER BYSELECT 之后执行,所以你可以ORDER BY 子句中使用 SELECT 中定义的别名。
  8. LIMIT:限制返回行数。

    • 这是整个流水线的最后一步。在结果集已经排序完成后,LIMIT 会从中选取指定范围的行返回给客户端。

直观的总结对比

序号 逻辑执行顺序 SQL书写顺序 作用说明
1 FROM / JOIN FROM 从哪个表获取数据,并进行连接,形成虚拟表。
2 WHERE WHERE 对虚拟表中的进行过滤。
3 GROUP BY GROUP BY 将行分组。
4 HAVING HAVING 分组进行过滤。
5 SELECT SELECT 选择要显示的列或表达式,并生成别名
6 DISTINCT DISTINCT SELECT 后的结果去重。
7 ORDER BY ORDER BY 对最终结果集进行排序。
8 LIMIT LIMIT 从排序后的结果集中,取出指定数量的行。

一个实例贯穿全程

假设我们有 studentsscores 两张表。

sql
-- 查询每个科目平均分超过75分的,并且学生人数超过10人的科目,
-- 按平均分降序排列,只显示科目名称和平均分。

SELECT
    s.subject AS '科目',          -- 第 5 步: 选择列并应用别名
    AVG(s.score) AS '平均分'     -- 第 5 步: 计算聚合函数并应用别名
FROM
    scores s                     -- 第 1 步: 确定主表 scores 并起别名 s
JOIN
    students st ON s.student_id = st.id -- (假设有JOIN)
WHERE
    st.grade = '三年级'            -- 第 2 步: 过滤行,只保留三年级的学生成绩
GROUP BY
    s.subject                    -- 第 3 步: 按科目进行分组
HAVING
    AVG(s.score) > 75            -- 第 4 步: 过滤分组,只保留平均分 > 75 的科目
    AND COUNT(s.student_id) > 10 -- 第 4 步: 过滤分组,只保留人数 > 10 的科目
ORDER BY
    `平均分` DESC                -- 第 7 步: 按 SELECT 中定义的别名 `平均分` 降序排序
LIMIT 10;                        -- 第 8 步: 取排序后的前10条记录

为什么理解这个顺序很重要?

  1. 别名的使用:你不能在 WHERE 子句中使用 SELECT 中定义的列别名,因为 WHERESELECT 之前执行,此时别名还不存在。但你可以在 ORDER BY 中使用,因为 ORDER BYSELECT 之后执行。
  2. 聚合函数的位置:你不能在 WHERE 中使用聚合函数(如 COUNT(*)),因为 WHERE 操作的是行,还没有进行分组。聚合函数必须用在 HAVINGSELECT 列表、ORDER BY 子句中。
  3. 查询优化:了解执行顺序有助于你写出更高效的SQL。例如,尽量用 WHERE 提前过滤掉大量无关数据,这样后续的 GROUP BY 和其他操作处理的数据量就会大大减少,从而提升性能。
右滑查看面试常问