基于本文回答
0
评论

MySQL语句的执行顺序

知识点图片

本文详细阐述了MySQL查询的逻辑执行顺序,从FROMLIMIT,这对于理解别名使用、聚合函数和SQL优化至关重要。

我们通常编写SQL的顺序是 SELECT... FROM... WHERE... GROUP BY... HAVING... ORDER BY... LIMIT...,但这并不是数据库真正执行它的顺序。

SQL查询的逻辑执行顺序

下面是SQL SELECT 语句的逻辑执行顺序。请记住,这是逻辑上的顺序,MySQL的查询优化器可能会为了性能而调整实际的物理执行计划,但最终返回的结果必须与这个逻辑顺序所产生的结果一致。

  1. FROM

    • 作用:指定查询的数据来源,即基表。
    • 过程:如果查询涉及多个表,此阶段会执行笛卡尔积(Cartesian Product),生成一个包含所有表所有行组合的庞大虚拟表(Virtual Table, VT1)。
  2. ON

    • 作用:基于 JOIN 条件对笛卡尔积进行筛选。
    • 过程:根据 ON 子句中指定的连接条件,从 VT1 中筛选出符合条件的行,生成一个新的虚拟表(VT2)。
  3. JOIN

    • 作用:添加外部行(针对 OUTER JOIN,如 LEFT JOIN, RIGHT JOIN)。
    • 过程:如果是 OUTER JOIN,会将主表中在 ON 条件中没有匹配到从表的行重新添加回 VT2,从表中对应的列填充为 NULL,生成虚拟表(VT3)。如果是 INNER JOIN,此步骤可以忽略。
  4. WHERE

    • 作用:对数据进行行级过滤。
    • 过程:根据 WHERE 子句中指定的条件,逐行检查 VT3,只保留计算结果为 TRUE 的行,生成虚拟表(VT4)。
    • 注意WHERE 子句中不能使用聚合函数(如 SUM(), AVG()),因为它在分组之前执行。
  5. GROUP BY

    • 作用:将数据按指定的列进行分组。
    • 过程:将 VT4 中的行按照 GROUP BY 子句指定的列进行分组,每个分组形成一条记录,生成虚拟表(VT5)。
  6. HAVING

    • 作用:对分组后的结果进行过滤。
    • 过程:根据 HAVING 子句中指定的条件,对 VT5 中的分组进行筛选,只保留计算结果为 TRUE 的分组,生成虚拟表(VT6)。
    • 注意HAVING 子句中可以使用聚合函数,因为它在分组之后执行。
  7. SELECT

    • 作用:选择要显示的列或表达式。
    • 过程:处理 VT6,计算所有表达式(包括聚合函数、标量函数等),并选择最终要显示的列,生成虚拟表(VT7)。此阶段会处理 DISTINCT 关键字(如果存在),用于去除重复行。
    • 注意:在此阶段,为列计算的别名(Alias)才生成。
  8. ORDER BY

    • 作用:对最终结果集进行排序。
    • 过程:根据 ORDER BY 子句指定的列和排序规则,对 VT7 中的行进行排序,生成虚拟表(VT8)。
    • 注意:可以使用 SELECT 阶段生成的列别名进行排序。
  9. LIMIT / OFFSET

    • 作用:限制返回的行数。
    • 过程:从已排序的 VT8 中,选择指定范围的行作为最终的查询结果返回给客户端。

总结与示例

简记顺序
FROM -> ON -> JOIN -> WHERE -> GROUP BY -> HAVING -> SELECT -> DISTINCT -> ORDER BY -> LIMIT

示例查询:

假设我们有两个表:employees (员工表) 和 departments (部门表)。我们要查询2020年后入职的、平均工资超过5000的部门名称,并按平均工资降序排列,只显示最高的那个部门。

我们写的SQL:

sql
SELECT
    d.dept_name,
    AVG(e.salary) AS avg_salary
FROM
    employees e
JOIN
    departments d ON e.dept_id = d.dept_id
WHERE
    e.hire_date > '2020-01-01'
GROUP BY
    d.dept_name
HAVING
    avg_salary > 5000  -- 注意:MySQL允许在HAVING中使用别名,但标准SQL不允许
ORDER BY
    avg_salary DESC
LIMIT 1;

数据库的逻辑执行过程:

  1. FROM employees e JOIN departments d: 计算 employees 表和 departments 表的笛卡尔积。
  2. ON e.dept_id = d.dept_id: 筛选出两个表中 dept_id 相等的行,形成一个包含员工和其对应部门信息的大表。
  3. WHERE e.hire_date > '2020-01-01': 从上一步的结果中,过滤掉入职日期不晚于 '2020-01-01' 的员工行。
  4. GROUP BY d.dept_name: 将剩余的行按照部门名称 (dept_name) 进行分组。每个部门成为一个独立的组。
  5. HAVING avg_salary > 5000: 对每个分组计算平均工资 AVG(e.salary),然后筛选出平均工资大于5000的分组。
  6. SELECT d.dept_name, AVG(e.salary) AS avg_salary: 从通过 HAVING 筛选后的分组中,提取出 dept_name 列和计算出的平均工资,并为平均工资赋予别名 avg_salary
  7. ORDER BY avg_salary DESC: 将上一步得到的结果集,按照 avg_salary 列进行降序排序。
  8. LIMIT 1: 从排序后的结果中,只取第一行。

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

  • 别名的使用:解释了为什么 ORDER BY 可以使用 SELECT 中定义的别名,而 WHEREGROUP BY 通常不能(因为它们在 SELECT 之前执行)。
  • 性能优化WHERE 子句在 GROUP BY 之前执行,这意味着你应该尽可能地在 WHERE 中过滤掉不需要的数据,这样可以大大减少分组和聚合时需要处理的数据量,从而提高查询效率。
  • 逻辑正确性:确保你理解聚合函数(如 SUM, AVG)只能在 GROUP BY 之后(即在 HAVING, SELECT, ORDER BY 中)使用的原因,避免在 WHERE 子句中误用。
右滑查看面试常问