1. 一条SQL语句的"执行剧本":先别急着背口诀
很多人一开始接触SQL执行顺序,第一反应就是去背那串口诀:FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT。背是背下来了,可真到写复杂查询的时候,照样懵。为什么?因为口诀背的是"逻辑执行顺序",而数据库真正跑起来的时候,是另一套"物理执行计划"——中间还藏着优化器这个"大管家"。
我在实际项目里见过太多类似的情况:某人写了个大JOIN,觉得"我WHERE里过滤了呀,怎么还这么慢",结果一看执行计划,WHERE条件被优化器推到了JOIN之后才生效。还有人在GROUP BY后面想用SELECT里的别名,报错之后一脸无辜:"不是先执行SELECT吗?"
所以这篇文章我不打算只给你口诀。我想带你从"数据库到底在干什么"的角度,把select、from、join、where、group by、having、order by、limit这条链路彻底捋清楚。搞懂它,你写SQL的时候就不只是"能跑",而是"知道它怎么跑、为什么这么跑、慢在哪、怎么改"。
这篇文章适合谁?刚入行的数据分析师、后端开发、运维,以及所有写过几条SQL但从来没看过执行计划的人。读完你会收获三样东西:一条能默写出来的执行链路、一套判断SQL性能瓶颈的思考框架,还有几个我从真实故障里总结出来的坑。
2. 从用户视角到数据库视角:两种顺序的"错位"
2.1 你写的顺序,不等于数据库做的顺序
先看一句最常见的 SQL:
SELECT department, COUNT(*) AS emp_count, AVG(salary) AS avg_salary FROM employees WHERE hire_date >= '2023-01-01' GROUP BY department HAVING COUNT(*) > 5 ORDER BY emp_count DESC LIMIT 10;你写代码的时候,大脑的思考顺序是:先从 employees 表里"捞"数据 → 过滤掉 2023 年之前的 → 按部门分组 → 算人数和平均工资 → 过滤掉人数少于等于 5 的 → 按人数排序 → 取前 10 条。
这个思考过程完全合理,因为它符合人类的直觉:先选数据,再处理数据。但数据库不这么干。数据库的"逻辑执行顺序"是这样的:
- FROM:确定数据源,加载表
- JOIN:如果有连接,先做连接操作
- WHERE:对"源数据 + 连接结果"做行级过滤
- GROUP BY:把过滤后的数据分组
- HAVING:对分组后的结果做过滤
- SELECT:计算你要的列、聚合函数、表达式
- ORDER BY:排序
- LIMIT:截断
看出来了吗?**WHERE 跑在 GROUP BY 之前,而 SELECT 里的别名要到 ORDER BY 之前才算出来。**这就是为什么你在 WHERE 里不能用 SELECT 的别名,在 GROUP BY 里通常也不能用 SELECT 的别名(MySQL 某些版本放宽了,但语义上依然不推荐)——因为执行到 WHERE 的时候,SELECT 的投影计算压根还没发生。
2.2 为什么非要用这种"反直觉"的顺序?
数据库这么做,核心原因是为了"尽早减少数据量"。数据量越小,后续每一步的计算成本越低。如果先 SELECT 再 WHERE,那你得先把所有满足 FROM 的数据都做投影计算,生成一份可能很大的中间结果,再去过滤——纯粹是浪费。 WHERE 先跑,相当于在流水线的最前面就把废料扔掉了,后面的 GROUP BY、ORDER BY 处理的是"瘦身"之后的数据。
用一个生活化的类比:你要从一整车水果里挑出又大又红的苹果,称重后按重量排序。你不会先把所有水果都拍张照片(SELECT),再挑苹果(WHERE)——那太蠢了。你肯定是先挑苹果、再称重、再排序。SQL 的执行顺序本质上是同一套"先缩小规模,再精细处理"的思路。
3. 逐层拆解:每个关键字到底在干什么
3.1 FROM 和 JOIN:先搭好数据的"工作台"
FROM 是整个查询的地基。它决定了你要从哪张表、哪个视图、甚至哪个子查询里取数据。这一步在逻辑上会生成一个"虚拟表",这个虚拟表在物理上不一定真实存在,但它决定了后续所有操作的"数据边界"。
这里有个关键认知:如果查询里有多张表,FROM 阶段其实会先做一个"笛卡尔积"——也就是把每张表的每一行都和另一张表的每一行组合一遍。比如 A 表 100 行、B 表 200 行,笛卡尔积就是 20000 行。这显然很可怕,所以实际执行时数据库优化器会尽量用 JOIN 条件来避免真正的笛卡尔积,但逻辑上你要理解:JOIN 条件就是用来"约束"这个组合过程的。
JOIN 在逻辑执行顺序上和 FROM 是同一个阶段。你写FROM a JOIN b ON a.id = b.a_id,数据库会先把 a 和 b 通过连接条件组合成一张大虚拟表,然后再交给 WHERE。这也解释了