qinyelin
发布于 2026-09-10 / 6 阅读
0
0

SQL逻辑执行顺序

面试频率:★★★★☆
工作频率:★★★★★


⚡ 30 秒速记

SQL 的书写顺序:

SELECT
FROM
JOIN
ON
WHERE
GROUP BY
HAVING
ORDER BY
LIMIT

但是 SQL 的逻辑执行顺序大致是:

FROM
 ↓
JOIN
 ↓
ON
 ↓
WHERE
 ↓
GROUP BY
 ↓
HAVING
 ↓
SELECT
 ↓
DISTINCT
 ↓
ORDER BY
 ↓
LIMIT

一句话记忆:

找表
↓
连表
↓
匹配
↓
过滤行
↓
分组
↓
过滤组
↓
选字段
↓
去重
↓
排序
↓
分页

最重要的三个知识点:

WHERE
=
GROUP BY之前过滤行


HAVING
=
GROUP BY之后过滤组


ORDER BY
=
逻辑上在SELECT之后
所以通常可以使用SELECT别名

注意:

这是 SQL 的逻辑执行顺序,不代表 MySQL 内部真正严格按照这个顺序物理执行。实际执行方式由优化器决定,可以通过 EXPLAIN 查看执行计划。


🎤 面试回答

面试官:

SQL 的执行顺序是什么?

可以回答:

SQL 的逻辑执行顺序大致是 FROM、JOIN、ON、WHERE、GROUP BY、HAVING、SELECT、DISTINCT、ORDER BY、LIMIT。

FROM、JOIN 和 ON 先确定数据来源以及表之间的关联关系,WHERE 在分组之前过滤数据行,然后 GROUP BY 进行分组,HAVING 对分组后的结果进行过滤。

接着 SELECT 选择需要返回的字段,DISTINCT 进行去重,ORDER BY 对结果进行排序,最后 LIMIT 限制返回的数据量。

需要注意,这只是 SQL 的逻辑处理顺序,并不代表 MySQL 实际严格按照这个顺序物理执行。MySQL 优化器会根据索引、统计信息和成本生成执行计划,实际执行情况可以通过 EXPLAIN 分析。


一、先看一个完整 SQL

例如:

SELECT department, COUNT(*) AS cnt
FROM employee e
JOIN company c
ON e.company_id = c.id
WHERE e.age > 20
GROUP BY department
HAVING COUNT(*) > 5
ORDER BY cnt DESC
LIMIT 10;

SQL 写出来是:

SELECT
↓
FROM
↓
JOIN
↓
ON
↓
WHERE
↓
GROUP BY
↓
HAVING
↓
ORDER BY
↓
LIMIT

但是逻辑上可以理解为:

FROM employee
      ↓
JOIN company
      ↓
ON e.company_id = c.id
      ↓
WHERE e.age > 20
      ↓
GROUP BY department
      ↓
HAVING COUNT(*) > 5
      ↓
SELECT department, COUNT(*) AS cnt
      ↓
ORDER BY cnt DESC
      ↓
LIMIT 10

二、FROM

第一步:

FROM employee

作用:

确定数据来源

可以理解:

我要查询数据
↓
先确定从哪里获取数据

所以:

FROM
=
确定数据来源

三、JOIN

如果查询涉及多张表:

FROM employee e
JOIN company c

就需要:

JOIN

把表关联起来。

例如:

employee
    ↓
JOIN
    ↓
company

但是:

两张表根据什么关联?

这就需要:

ON

四、ON

例如:

ON e.company_id = c.id

作用:

确定JOIN匹配条件

例如:

employee

张三 company_id = 1
李四 company_id = 2

company:

id    name

1     公司A
2     公司B

根据:

e.company_id = c.id

匹配:

张三
company_id = 1
       ↓
company.id = 1
       ↓
公司A

所以:

FROM
↓
JOIN
↓
ON

确定:

数据来源
+
关联关系

五、WHERE

接下来:

WHERE e.age > 20

作用:

过滤数据行

例如:

张三 18岁 ❌

李四 20岁 ❌

王五 21岁 ✅

赵六 25岁 ✅

所以:

WHERE
=
过滤行

最重要的是:

WHERE
↓
GROUP BY

也就是:

WHERE 在 GROUP BY 之前过滤数据。


六、GROUP BY

经过 WHERE:

张三  技术部
李四  技术部
王五  销售部
赵六  技术部
小明  销售部

执行:

GROUP BY department

按照部门进行分组:

技术部
├── 张三
├── 李四
└── 赵六


销售部
├── 王五
└── 小明

所以:

GROUP BY
=
分组

完整过程:

原始数据
↓
WHERE
↓
过滤不需要的数据
↓
GROUP BY
↓
对剩余数据进行分组

七、HAVING

GROUP BY 以后:

技术部 → 10人

销售部 → 8人

财务部 → 3人

执行:

HAVING COUNT(*) > 5

得到:

技术部 → 10人 ✅

销售部 → 8人  ✅

财务部 → 3人  ❌

所以:

HAVING
=
过滤分组后的结果

八、WHERE 和 HAVING 的区别

这是最经典的面试题之一。

WHERE

GROUP BY之前
↓
过滤数据行

例如:

WHERE age > 20

过滤的是:

员工

HAVING

GROUP BY之后
↓
过滤分组结果

例如:

HAVING COUNT(*) > 5

过滤的是:

部门分组

所以:

WHERE
↓
过滤行

GROUP BY
↓
分组

HAVING
↓
过滤组

一句话:

WHERE 是分组前过滤行,HAVING 是分组后过滤组。


九、为什么聚合函数过滤经常使用 HAVING?

例如:

SELECT department, COUNT(*)
FROM employee
GROUP BY department
HAVING COUNT(*) > 5;

因为:

COUNT(*)

是对:

分组结果

进行统计。

逻辑:

employee
↓
GROUP BY department
↓
形成部门分组
↓
COUNT(*)
↓
统计每组数量
↓
HAVING COUNT(*) > 5
↓
过滤分组

所以:

针对聚合结果过滤

通常使用:

HAVING

十、SELECT

经过:

FROM
JOIN
ON
WHERE
GROUP BY
HAVING

之后,逻辑上再处理:

SELECT department, COUNT(*) AS cnt

也就是:

选择最终需要返回的字段 / 表达式

例如:

department    cnt

技术部         10
销售部         8

十一、为什么 WHERE 通常不能直接使用 SELECT 别名?

例如:

SELECT age AS a
FROM employee
WHERE a > 20;

为什么不能想当然这么写?

看逻辑顺序:

WHERE
↓
SELECT

也就是说:

WHERE逻辑处理的时候

SELECT:

age AS a

这个别名逻辑上还没有产生。

可以理解:

WHERE a > 20
↓
a是谁?
↓
还没有SELECT


SELECT age AS a
↓
这里才产生a

所以应该:

SELECT age AS a
FROM employee
WHERE age > 20;

注意:

不同数据库对别名的具体语法支持可能存在差异,但用逻辑执行顺序理解最清楚。


十二、DISTINCT

例如:

SELECT DISTINCT department
FROM employee;

可以理解:

SELECT department
↓
得到结果
↓
DISTINCT
↓
去重

例如:

技术部
技术部
销售部
技术部
销售部

经过 DISTINCT:

技术部
销售部

所以:

DISTINCT
=
结果去重

十三、ORDER BY

然后:

ORDER BY cnt DESC

作用:

排序

例如:

技术部 10
销售部 8
研发部 20

执行:

ORDER BY cnt DESC

得到:

研发部 20
技术部 10
销售部 8

十四、为什么 ORDER BY 可以使用 SELECT 别名?

例如:

SELECT salary * 12 AS year_salary
FROM employee
ORDER BY year_salary DESC;

逻辑顺序:

SELECT
↓
产生year_salary
↓
ORDER BY
↓
使用year_salary排序

所以:

ORDER BY

通常可以使用:

SELECT别名

十五、WHERE 和 ORDER BY 使用别名的区别

假设:

SELECT salary * 12 AS year_salary
FROM employee;

逻辑:

WHERE
↓
SELECT
↓
ORDER BY

所以:

WHERE
↓
SELECT之前
↓
通常不能使用SELECT别名

而:

ORDER BY
↓
SELECT之后
↓
通常可以使用SELECT别名

一张图:

WHERE
  ↓
SELECT
  ↓
产生 year_salary
  ↓
ORDER BY
  ↓
可以使用 year_salary

十六、LIMIT

最后:

LIMIT 10

作用:

限制最终返回的数据量

例如:

SELECT *
FROM employee
ORDER BY salary DESC
LIMIT 10;

逻辑:

employee
↓
形成结果
↓
按照salary降序
↓
LIMIT 10
↓
返回前10条

也就是:

工资最高的10条记录

十七、ORDER BY 和 LIMIT 的关系

例如:

SELECT *
FROM orders
ORDER BY create_time DESC
LIMIT 20;

逻辑:

订单数据
↓
ORDER BY create_time DESC
↓
最新数据排在前面
↓
LIMIT 20
↓
取前20条

所以:

ORDER BY
↓
LIMIT

逻辑上:

先排序,再限制返回数量。

这在:

分页

Top N查询

最新数据查询

中非常常见。


十八、和深分页串起来

之前学过:

SELECT *
FROM orders
ORDER BY id
LIMIT 5000000, 10;

逻辑上:

FROM orders
↓
得到满足条件的数据
↓
ORDER BY id
↓
按照id排序
↓
LIMIT 5000000,10
↓
跳过大量前置记录
↓
返回10条

所以:

LIMIT

逻辑上位于:

ORDER BY

之后。

也能帮助理解为什么:

大OFFSET

可能带来较高成本。


十九、完整案例执行流程

SQL:

SELECT department, COUNT(*) AS cnt
FROM employee e
JOIN company c
ON e.company_id = c.id
WHERE e.age > 20
GROUP BY department
HAVING COUNT(*) > 5
ORDER BY cnt DESC
LIMIT 10;

完整逻辑:

FROM employee
      ↓
确定employee数据源
      ↓
JOIN company
      ↓
关联company
      ↓
ON e.company_id = c.id
      ↓
确定关联条件
      ↓
WHERE e.age > 20
      ↓
过滤20岁及以下员工
      ↓
GROUP BY department
      ↓
按照部门分组
      ↓
HAVING COUNT(*) > 5
      ↓
只保留人数大于5的部门
      ↓
SELECT department, COUNT(*) AS cnt
      ↓
选择部门和人数
      ↓
ORDER BY cnt DESC
      ↓
按照人数降序
      ↓
LIMIT 10
      ↓
返回前10条

二十、一张图彻底记住

                 SQL
                  ↓
             FROM employee
                  ↓
              确定数据源
                  ↓
             JOIN company
                  ↓
          ON company_id = id
                  ↓
              完成表关联
                  ↓
           WHERE age > 20
                  ↓
                过滤行
                  ↓
        GROUP BY department
                  ↓
                 分组
                  ↓
        HAVING COUNT(*) > 5
                  ↓
                过滤组
                  ↓
 SELECT department, COUNT(*) AS cnt
                  ↓
              选择返回字段
                  ↓
               DISTINCT
                  ↓
                  去重
                  ↓
          ORDER BY cnt DESC
                  ↓
                  排序
                  ↓
               LIMIT 10
                  ↓
                最终结果

🎯 面试追问

Q1:WHERE 和 HAVING 有什么区别?

答:

WHERE
↓
GROUP BY之前
↓
过滤数据行
HAVING
↓
GROUP BY之后
↓
过滤分组结果

一句话:

WHERE 过滤行,HAVING 过滤组。


Q2:为什么聚合条件通常写 HAVING?

例如:

HAVING COUNT(*) > 5

因为:

GROUP BY
↓
形成分组
↓
产生聚合结果
↓
HAVING
↓
过滤聚合结果

Q3:为什么 WHERE 通常不能直接使用 SELECT 别名?

因为逻辑顺序:

WHERE
↓
SELECT

WHERE 处理时:

SELECT别名逻辑上还没有产生

Q4:为什么 ORDER BY 通常可以使用 SELECT 别名?

因为:

SELECT
↓
产生别名
↓
ORDER BY

所以:

ORDER BY

通常可以使用 SELECT 别名。


Q5:ORDER BY 和 LIMIT 谁先?

逻辑上:

ORDER BY
↓
LIMIT

也就是:

先排序
↓
再取数据

Q6:WHERE 和 GROUP BY 谁先?

WHERE
↓
GROUP BY

也就是:

先过滤
↓
再分组

Q7:GROUP BY 和 HAVING 谁先?

GROUP BY
↓
HAVING

也就是:

先分组
↓
再过滤组

Q8:SELECT 和 ORDER BY 谁先?

逻辑上:

SELECT
↓
ORDER BY

这也是 ORDER BY 通常可以使用 SELECT 别名的原因之一。


Q9:LIMIT 为什么在最后?

因为:

前面的查询结果
↓
完成过滤 / 分组 / 排序等逻辑
↓
LIMIT
↓
限制最终返回数量

Q10:SQL 逻辑执行顺序就是 MySQL 真实执行顺序吗?

不是。

逻辑执行顺序
=
帮助理解SQL语义

真实执行:

SQL
↓
MySQL优化器
↓
成本估算
↓
生成执行计划
↓
执行

实际执行计划:

通过EXPLAIN分析

二十一、逻辑顺序和真实执行计划的区别

这里一定不要混淆。

我们学习:

FROM
↓
WHERE
↓
GROUP BY
↓
HAVING
↓
SELECT
↓
ORDER BY
↓
LIMIT

是为了理解:

SQL语义

例如:

为什么WHERE在分组前过滤?

为什么HAVING可以过滤COUNT结果?

为什么ORDER BY可以使用SELECT别名?

但是 MySQL 真正执行时:

不会机械地严格按照这张图
一步一步执行

因为 MySQL 有:

优化器

优化器会考虑:

索引

数据量

统计信息

JOIN顺序

覆盖索引

ICP

查询成本

然后:

生成执行计划

二十二、逻辑执行顺序和 EXPLAIN

可以这样区分:

SQL逻辑执行顺序
        ↓
理解SQL语义

例如:

WHERE
GROUP BY
HAVING
SELECT
ORDER BY
LIMIT

而:

EXPLAIN
        ↓
分析MySQL执行计划

重点看:

type
key
rows
Extra

所以:

逻辑执行顺序
=
理论上这条SQL表达什么意思
EXPLAIN
=
MySQL准备怎么把它执行出来

二十三、和之前的 MySQL 知识串起来

现在很多知识已经可以串起来:

FROM
↓
JOIN
↓
ON
↓
JOIN原理与索引优化
WHERE
↓
索引
↓
联合索引
↓
最左匹配
↓
索引失效
↓
ICP
GROUP BY
↓
GROUP BY索引优化
ORDER BY
↓
ORDER BY索引优化
LIMIT
↓
LIMIT深分页优化

最后:

EXPLAIN
↓
验证实际执行计划

整个体系:

                    SQL
                     ↓
          ┌──────────┴──────────┐
          ↓                     ↓
     逻辑执行顺序           实际执行计划
          ↓                     ↓
       SQL语义               EXPLAIN
          ↓                     ↓
WHERE / GROUP BY        type / key / rows
HAVING / ORDER BY             / Extra
LIMIT

⚠️ 易错点

1. SQL 书写顺序 ≠ SQL 逻辑执行顺序

书写:

SELECT
FROM
WHERE
GROUP BY
HAVING
ORDER BY
LIMIT

逻辑:

FROM
WHERE
GROUP BY
HAVING
SELECT
ORDER BY
LIMIT

不要混淆。


2. WHERE 和 HAVING 不能完全当成一样的东西

错误:

WHERE和HAVING都是过滤
↓
随便用

正确:

WHERE
=
分组前过滤行
HAVING
=
分组后过滤组

3. WHERE 通常不能直接使用 SELECT 别名

因为:

WHERE
↓
SELECT

SELECT 别名逻辑上:

还没产生

4. ORDER BY 通常可以使用 SELECT 别名

因为:

SELECT
↓
ORDER BY

5. LIMIT 逻辑上在 ORDER BY 后

例如:

ORDER BY salary DESC
LIMIT 10;

表示:

先按照工资降序
↓
再取前10条

6. 逻辑执行顺序 ≠ 物理执行顺序

不要在面试时说:

MySQL一定先把FROM所有数据全部查出来
然后WHERE
然后GROUP BY
然后SELECT

更准确:

FROM、WHERE、GROUP BY、HAVING、SELECT、ORDER BY、LIMIT 描述的是 SQL 的逻辑处理顺序,实际物理执行由 MySQL 优化器根据成本生成执行计划。


❓ 自测

  1. SQL 的书写顺序是什么?

  2. SQL 的逻辑执行顺序是什么?

  3. FROM 的作用是什么?

  4. JOIN 的作用是什么?

  5. ON 的作用是什么?

  6. WHERE 是过滤行还是过滤组?

  7. WHERE 和 GROUP BY 谁先?

  8. GROUP BY 的作用是什么?

  9. HAVING 是过滤行还是过滤组?

  10. GROUP BY 和 HAVING 谁先?

  11. WHERE 和 HAVING 最大区别是什么?

  12. 为什么聚合函数过滤经常使用 HAVING?

  13. SELECT 在逻辑执行顺序中的位置在哪里?

  14. 为什么 WHERE 通常不能直接使用 SELECT 别名?

  15. DISTINCT 是干什么的?

  16. ORDER BY 是干什么的?

  17. 为什么 ORDER BY 通常可以使用 SELECT 别名?

  18. ORDER BY 和 LIMIT 谁先?

  19. LIMIT 的作用是什么?

  20. ORDER BY salary DESC LIMIT 10 表示什么?

  21. 为什么 WHERE age > 20 在 GROUP BY 之前?

  22. HAVING COUNT(*) > 5 过滤的是什么?

  23. SQL 逻辑执行顺序和 MySQL 真实执行顺序一样吗?

  24. MySQL 实际执行 SQL 由谁决定?

  25. EXPLAIN 是用来分析什么的?

  26. SQL 逻辑执行顺序和 EXPLAIN 分别解决什么问题?

  27. 为什么说逻辑执行顺序可以帮助理解 SQL,而不能完全代表底层执行过程?

  28. FROM、JOIN、ON 和你之前学习的 JOIN 优化有什么关系?

  29. ORDER BY、LIMIT 分别和之前哪些优化知识有关?

  30. 请完整说出 SQL 的逻辑执行顺序。


评论