MySQL语句的执行顺序
本文详细阐述了MySQL查询的逻辑执行顺序,从
FROM到LIMIT,这对于理解别名使用、聚合函数和SQL优化至关重要。
我们通常编写SQL的顺序是 SELECT... FROM... WHERE... GROUP BY... HAVING... ORDER BY... LIMIT...,但这并不是数据库真正执行它的顺序。
SQL查询的逻辑执行顺序
下面是SQL SELECT 语句的逻辑执行顺序。请记住,这是逻辑上的顺序,MySQL的查询优化器可能会为了性能而调整实际的物理执行计划,但最终返回的结果必须与这个逻辑顺序所产生的结果一致。
FROM- 作用:指定查询的数据来源,即基表。
- 过程:如果查询涉及多个表,此阶段会执行笛卡尔积(Cartesian Product),生成一个包含所有表所有行组合的庞大虚拟表(Virtual Table, VT1)。
ON- 作用:基于
JOIN条件对笛卡尔积进行筛选。 - 过程:根据
ON子句中指定的连接条件,从 VT1 中筛选出符合条件的行,生成一个新的虚拟表(VT2)。
- 作用:基于
JOIN- 作用:添加外部行(针对
OUTER JOIN,如LEFT JOIN,RIGHT JOIN)。 - 过程:如果是
OUTER JOIN,会将主表中在ON条件中没有匹配到从表的行重新添加回 VT2,从表中对应的列填充为NULL,生成虚拟表(VT3)。如果是INNER JOIN,此步骤可以忽略。
- 作用:添加外部行(针对
WHERE- 作用:对数据进行行级过滤。
- 过程:根据
WHERE子句中指定的条件,逐行检查 VT3,只保留计算结果为TRUE的行,生成虚拟表(VT4)。 - 注意:
WHERE子句中不能使用聚合函数(如SUM(),AVG()),因为它在分组之前执行。
GROUP BY- 作用:将数据按指定的列进行分组。
- 过程:将 VT4 中的行按照
GROUP BY子句指定的列进行分组,每个分组形成一条记录,生成虚拟表(VT5)。
HAVING- 作用:对分组后的结果进行过滤。
- 过程:根据
HAVING子句中指定的条件,对 VT5 中的分组进行筛选,只保留计算结果为TRUE的分组,生成虚拟表(VT6)。 - 注意:
HAVING子句中可以使用聚合函数,因为它在分组之后执行。
SELECT- 作用:选择要显示的列或表达式。
- 过程:处理 VT6,计算所有表达式(包括聚合函数、标量函数等),并选择最终要显示的列,生成虚拟表(VT7)。此阶段会处理
DISTINCT关键字(如果存在),用于去除重复行。 - 注意:在此阶段,为列计算的别名(Alias)才生成。
ORDER BY- 作用:对最终结果集进行排序。
- 过程:根据
ORDER BY子句指定的列和排序规则,对 VT7 中的行进行排序,生成虚拟表(VT8)。 - 注意:可以使用
SELECT阶段生成的列别名进行排序。
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;
数据库的逻辑执行过程:
FROM employees e JOIN departments d: 计算employees表和departments表的笛卡尔积。ON e.dept_id = d.dept_id: 筛选出两个表中dept_id相等的行,形成一个包含员工和其对应部门信息的大表。WHERE e.hire_date > '2020-01-01': 从上一步的结果中,过滤掉入职日期不晚于 '2020-01-01' 的员工行。GROUP BY d.dept_name: 将剩余的行按照部门名称 (dept_name) 进行分组。每个部门成为一个独立的组。HAVING avg_salary > 5000: 对每个分组计算平均工资AVG(e.salary),然后筛选出平均工资大于5000的分组。SELECT d.dept_name, AVG(e.salary) AS avg_salary: 从通过HAVING筛选后的分组中,提取出dept_name列和计算出的平均工资,并为平均工资赋予别名avg_salary。ORDER BY avg_salary DESC: 将上一步得到的结果集,按照avg_salary列进行降序排序。LIMIT 1: 从排序后的结果中,只取第一行。
为什么理解这个顺序很重要?
- 别名的使用:解释了为什么
ORDER BY可以使用SELECT中定义的别名,而WHERE和GROUP BY通常不能(因为它们在SELECT之前执行)。 - 性能优化:
WHERE子句在GROUP BY之前执行,这意味着你应该尽可能地在WHERE中过滤掉不需要的数据,这样可以大大减少分组和聚合时需要处理的数据量,从而提高查询效率。 - 逻辑正确性:确保你理解聚合函数(如
SUM,AVG)只能在GROUP BY之后(即在HAVING,SELECT,ORDER BY中)使用的原因,避免在WHERE子句中误用。
右滑查看面试常问