SQL查询语句的执行顺序到底是怎么样的?
很多开发者写了多年SQL,却还是会被“为什么别名不能在 WHERE 里用,却能在 ORDER BY 里用?”这类问题绊住。其实,这背后的根本原因就是:SQL 的逻辑执行顺序和我们的书写顺序完全不同。今天我们就以 MySQL 为例,把查询语句的执行顺序彻底讲透。
一、书写顺序 vs 执行顺序
我们习惯的书写顺序(从键盘输入的顺序)通常是这样的:
sql
SELECT 字段列表 FROM 表名 JOIN 表名 ON 连接条件 WHERE 筛选条件 GROUP BY 分组字段 HAVING 分组后筛选 ORDER BY 排序字段 LIMIT 偏移量, 行数;
然而,数据库引擎理解并执行这条 SQL 的逻辑顺序却是:
text
1. FROM / JOIN 2. ON 3. WHERE 4. GROUP BY 5. HAVING 6. SELECT 7. DISTINCT 8. ORDER BY 9. LIMIT / OFFSET
这两个顺序的不一致,就是很多“怪现象”的根源。下面我们逐行拆解每一步到底做了什么。
二、逐步拆解逻辑执行过程
1. FROM / JOIN
首先处理FROM子句,将目标表(以及JOIN涉及的表)组装起来,形成初始数据集。
如果有多表联接,会通过笛卡尔积生成虚拟表VT1。
2. ON
针对JOIN中的连接条件(即ON子句)进行筛选,只保留满足条件的行。
这时候会将那些没有匹配上的外连接行(比如LEFT JOIN中右表没有匹配的记录)以 NULL 填充,生成虚拟表VT2。
3. WHERE
对VT2应用WHERE条件,过滤掉不符合要求的行,得到VT3。
⚠️ 注意:因为
WHERE执行时,SELECT中的别名还没有被定义,所以WHERE里不能使用列别名。这就是WHERE column_alias = 'xxx'会报错的原因。
4. GROUP BY
根据GROUP BY指定的字段对VT3进行分组,生成VT4。
在 MySQL 中,因为扩展语法的存在,你可以在GROUP BY中使用SELECT里的别名(例如GROUP BY alias_name),但这不符合 SQL 标准,如果后续需要迁移数据库(如 PostgreSQL),这种写法就会失效,因此建议尽量少用。
5. HAVING
对分组后的结果VT4应用HAVING过滤条件,得到VT5。HAVING里通常配合聚合函数使用(比如HAVING COUNT(*) > 5)。
又一个常见的混淆点:标准 SQL 中 HAVING 也不能使用 SELECT 别名,因为 SELECT 还在后面。但 MySQL 允许这样做,可一旦使用,代码的可移植性就会变差。
6. SELECT
终于轮到SELECT!它从VT5中提取出你想要的列,并计算表达式、附上别名,输出虚拟表VT6。
到这里,你在SELECT中定义的别名才正式“诞生”。
7. DISTINCT
如果查询中指定了DISTINCT,就会对VT6做去重操作,得到VT7。
8. ORDER BY
对VT7按ORDER BY指定的规则进行排序,生成VT8。
因为此时SELECT中的别名早已存在,所以ORDER BY 可以直接使用别名,写起来非常自然。
9. LIMIT / OFFSET
最后一步,从VT8中截取指定范围的行,返回最终结果集。
三、实战:用一个例子走通全流程
假设有两张表:
emp(员工表):id, name, dept_id, salarydept(部门表):id, dept_name
需求:查询每个部门名称和平均工资,只保留平均工资大于 5000 的部门,按平均工资降序排列,取前 3 名。
sql
SELECT d.dept_name, AVG(e.salary) AS avg_sal FROM emp e JOIN dept d ON e.dept_id = d.id WHERE e.salary > 3000 GROUP BY d.dept_name HAVING avg_sal > 5000 ORDER BY avg_sal DESC LIMIT 3;
根据上面的逻辑顺序,这条 SQL 在 MySQL 中的执行流程如下:
FROM + JOIN + ON
将emp和dept通过e.dept_id = d.id连接,生成VT2。WHERE
从VT2中筛掉salary <= 3000的行,得到VT3。GROUP BY
按d.dept_name分组,产生每组内员工的薪资集合,得到VT4。HAVING
过滤掉AVG(salary) <= 5000的组,留下平均薪资大于 5000 的部门,得到VT5。
这里HAVING avg_sal > 5000能正常运行,得益于 MySQL 的扩展;在其他数据库中可能直接报错。SELECT
从VT5中提取d.dept_name并计算AVG(e.salary)命名为avg_sal,形成VT6。ORDER BY avg_sal DESC
对VT6按avg_sal降序排序,别名可用,得到VT8。LIMIT 3
截取排序后的前 3 行返回。
如果你还在查询中使用了窗口函数(如ROW_NUMBER() OVER(...)),它的计算阶段处于HAVING 之后、ORDER BY 之前,所以窗口函数的结果不能在WHERE、GROUP BY或HAVING中直接引用。
四、常见误区三则
WHERE 不能使用列别名
因为WHERE在SELECT之前执行,那时候别名甚至还没出生,必须用原列名或表达式。MySQL 中 GROUP BY 和 HAVING 都可以用别名,但不要滥用
虽然方便,却牺牲了 SQL 的可移植性,也可能混淆“逻辑执行顺序”这一核心认知。“执行顺序”不等于“优化器真实执行路径”
以上是 SQL 的逻辑处理阶段。MySQL 优化器实际执行时可能会把WHERE条件下推到索引扫描中、改变 JOIN 顺序等,但这些优化保证最终结果与逻辑顺序等价,因此理解逻辑顺序对编写及调试 SQL 仍然至关重要。
五、总结
SQL 的逻辑执行顺序:FROM → ON → WHERE → GROUP BY → HAVING → SELECT → DISTINCT → ORDER BY → LIMIT。
别名何时可用、何时不可用,全由这个顺序决定。
掌握这一点,不仅让你能写出更精准的查询,还能帮助你在排查慢 SQL、阅读执行计划时思路更清晰。
希望这篇博客能让你以后面对 SQL 执行顺序问题时,心里始终有一张清晰的“路线图”。
如果觉得有帮助,欢迎点赞、收藏、转发,感谢支持!
