MySQL 语句执行顺序

在MySQL中,查询语句的执行顺序并不是按照我们写SQL语句的顺序(SELECT、FROM、WHERE等)来执行的。了解实际执行顺序有助于我们理解查询逻辑和优化查询。

SQL语句的书写顺序:

  1. SELECT:选择要返回的列
  2. FROM:指定查询的表
  3. WHERE:对行进行过滤
  4. GROUP BY:对行进行分组
  5. HAVING:对分组后的组进行过滤
  6. ORDER BY:对结果进行排序
  7. LIMIT:限制返回的行数

SQL语句的实际执行顺序:

  1. FROM:首先确定查询的表,包括JOIN操作(如果有)
  2. ON:如果是JOIN,应用ON条件
  3. JOIN:执行连接操作
  4. WHERE:对行进行过滤,此时还没有分组,所以不能使用聚合函数
  5. GROUP BY:将数据按照指定的列分组
  6. HAVING:对分组后的组进行过滤,可以使用聚合函数(如COUNT, SUM等)
  7. SELECT:选择要返回的列,此时可以:
    1. 计算表达式
    2. 使用聚合函数(因为分组已经完成)
    3. 为列取别名(这些别名在后续步骤中可以被使用,但在WHERE和GROUP BY中不能使用)
  8. DISTINCT:去重(如果有DISTINCT关键字)
  9. ORDER BY:对结果集进行排序,可以使用SELECT中定义的别名
  10. LIMIT:限制返回的行数

SQL 逻辑执行顺序 (超详细版)

我们将以一个包含 JOIN 的复杂查询为例,逐一分解这个流水线上的每一个步骤。

查询的逻辑执行流程图
(原始表) --> 1. FROM/JOIN --> (VT1) --> 2. WHERE --> (VT2) --> 3. GROUP BY --> (VT3) --> 4. HAVING --> (VT4) --> 5. SELECT --> (VT5) --> 6. DISTINCT --> (VT6) --> 7. ORDER BY --> (VT7) --> 8. LIMIT --> (最终结果)

这里的 VT 代表虚拟表 (Virtual Table),它是上一步操作产生的结果集,作为下一步操作的输入。


步骤 1: FROM & JOIN (数据源的构建)
  • 核心动作: 这是整个查询的起点。数据库会根据 FROM 子句和 JOIN 子句,构建出本次查询所需要的数据源。
  • 详细过程:
    • 交叉连接 (Cross Join): 引擎首先会查看 FROM 子句中的所有表。在逻辑上,它会先对这些表进行“笛卡尔积”,生成一个包含了所有表行组合的巨大虚拟表。例如,tableA 有100行,tableB 有100行,那么笛卡尔积就有 100 * 100 = 10000 行。
    • ON 条件过滤: 接着,ON 子句开始工作。它会遍历笛卡尔积产生的巨大虚拟表,逐行应用 ON 后面的连接条件进行过滤。只有满足 ON 条件的行才会被保留下来。这是将无意义的行组合剔除,只保留相关联数据的关键一步。
    • 添加外部行 (OUTER JOIN): 如果你使用的是 LEFT JOINRIGHT JOIN,在 ON 过滤之后,还会有一个附加步骤。以 LEFT JOIN 为例,引擎会检查左表中是否有任何行在 ON 过滤后没有在结果中找到匹配项。如果有,这些左表的行会被重新添加回结果集,其右表对应的列则用 NULL 值填充。RIGHT JOIN 同理。
  • 产出: 一个包含了所有连接、筛选和(可能有的)NULL 填充的虚拟表,我们称之为 VT1。之后的所有操作都将基于这个 VT1 进行。
  • 示例:
FROM employees e
LEFT JOIN departments d ON e.department_id = d.id

这里会先将 employeesdepartments 做笛卡尔积,然后用 e.department_id = d.id 筛选,最后把 employees 表里没有匹配到部门的员工行再加回来,d 表的列填充为 NULL


步骤 2: WHERE (行级过滤器)
  • 核心动作: 对 VT1 中的每一行数据进行过滤。
  • 输入: 步骤 1 产生的虚拟表 VT1。
  • 详细过程: WHERE 子句会逐行扫描 VT1,判断每一行是否满足 WHERE 后面的条件。如果条件为真 (TRUE),该行被保留;如果为假 (FALSE) 或未知 (UNKNOWN),该行被永久丢弃
  • 产出: 一个行数更少(或相等)的虚拟表 VT2
  • 示例:
SELECT `name` ,department FROM employees WHERE `name` = "张三";

  • 关键限制: 因为 WHERESELECT 之前执行,所以此时 SELECT 子句中定义的列别名是不可用的。你只能使用 VT1 中真实存在的列。
SELECT `name` as n ,department FROM employees WHERE n = "张三";


步骤 3: GROUP BY (数据分组)
  • 核心动作: 将相似的行合并成一个摘要行。
  • 输入: 步骤 2 产生的虚拟表 VT2。
  • 详细过程: GROUP BY 子句会根据指定的列,将 VT2 中具有相同值的行分为一组。每个组在逻辑上会变成一行
  • 产出: 一个新的虚拟表 VT3,其行数等于 VT2 中不重复的分组数量。
  • 示例:
SELECT * FROM employees GROUP BY department;

这是常见的错误,这个错误是由于 MySQL 的 sql_mode 包含 ONLY_FULL_GROUP_BY 模式导致的,该模式要求 SELECT 列表中的非聚合列必须出现在 GROUP BY 子句中或具有函数依赖性

  • 解决方案
-- 临时禁用(当前会话)
SET SESSION sql_mode=(SELECT REPLACE(@@sql_mode,'ONLY_FULL_GROUP_BY',''));
-- 永久禁用(需修改my.cnf配置文件)
[mysqld]
sql_mode=STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION

  • 重要影响: 从这一步开始,查询的粒度从“单行”变为了“分组”。因此,后续的 SELECTHAVING 子句中,你只能引用:
    • GROUP BY 中使用的列。
    • 聚合函数(COUNT(), SUM(), AVG(), MAX(), MIN()),因为它们是针对整个分组进行计算的。

步骤 4: HAVING (分组过滤器)
  • 核心动作: 对 GROUP BY 之后形成的分组进行过滤。
  • 输入: 步骤 3 产生的虚拟表 VT3。
  • 详细过程: HAVING 子句会遍历 VT3 中的每一个分组(摘要行),应用其后的条件。不满足条件的整个分组将被丢弃。
  • 产出: 一个分组数量更少(或相等)的虚拟表 VT4
  • 示例:
-- 部门人数大于8的部门
SELECT department FROM employees GROUP BY department HAVING COUNT(name) > 8;

  • 与 WHERE 的核心区别:
    • WHERE:过滤,在分组前工作,不能使用聚合函数。
    • HAVING:过滤分组,在分组后工作,可以(也通常会)使用聚合函数。
步骤 5: SELECT (最终列的投影)
  • 核心动作: 计算并选择最终要显示的列。
  • 输入: 步骤 4 产生的虚拟表 VT4。
  • 详细过程: 这是数据库第一次,也是唯一一次处理 SELECT 列表。它会执行以下操作:
    • 计算表达式:如 salary * 1.1
    • 调用函数:如 UPPER(name)AVG(salary)
    • 生成列别名: 如此时 AVG(salary) AS average_salary 中的 average_salary 才被创建。
  • 产出: 一个新的虚拟表 VT5,包含了最终要展示的所有列和计算结果。

步骤 6: DISTINCT (结果去重)
  • 核心动作: 移除结果集中的重复行。
  • 输入: 步骤 5 产生的虚拟表 VT5。
  • 详细过程: 如果使用了 DISTINCT 关键字,数据库会扫描 VT5,并移除所有完全重复的行(即所有列的值都相同的行)。
  • 产出: 一个行数可能更少的虚拟表 VT6

步骤 7: ORDER BY (结果排序)
  • 核心动作: 对最终的结果集进行排序。
  • 输入: 步骤 6 产生的虚拟表 VT6。
  • 详细过程: ORDER BY 子句会根据指定的列和排序规则(ASCDESC)对 VT6 中的所有行进行排序。
  • 产出: 一个行顺序确定的虚拟表 VT7
  • 关键点: 因为 ORDER BYSELECT 之后执行,所以 SELECT 中定义的列别名在这里是可用的,并且强烈推荐使用别名,这能让代码更清晰。

步骤 8: LIMIT / OFFSET (分页)
  • 核心动作: 从已排序的结果集中,选取一个子集。
  • 输入: 步骤 7 产生的虚拟表 VT7。
  • 详细过程: 这是流水线的最后一步。LIMIT 指定了最多返回多少行,OFFSET 指定了从第几行开始。
  • 产出: 发送给客户端的最终结果集

一点补充:关于查询优化器

上面描述的是逻辑执行顺序,这是一个概念模型,帮助我们理解 SQL 的行为和规则。而 MySQL 的查询优化器 在实际执行时,为了提高效率,可能会改变物理执行顺序。例如:

  • 它可能会将 WHERE 条件下推到存储引擎层,以便在读取数据时就进行过滤。
  • 它可能会根据统计信息和索引,决定 JOIN 的顺序(先连接哪个表)。

但是,优化器所做的任何改变都必须保证最终结果与逻辑执行顺序的结果完全一致

总结表格(增强版)

逻辑顺序

关键字

输入虚拟表

核心动作

输出虚拟表

是否可用别名?

1

FROM / JOIN

原始表

构建数据源,通过 ON 过滤,处理 OUTER JOIN

VT1

-

2

WHERE

VT1

过滤

VT2

3

GROUP BY

VT2

将行合并成分组

VT3

4

HAVING

VT3

过滤分组

VT4

(MySQL有扩展支持)

5

SELECT

VT4

选择列,计算表达式,生成别名

VT5

-

6

DISTINCT

VT5

去除重复行

VT6

-

7

ORDER BY

VT6

对结果进行排序

VT7

8

LIMIT

VT7

选取最终的子集

最终结果

希望这次更详尽的、包含了 JOIN 和虚拟表概念的解释,能帮助您彻底理解 MySQL 语句的执行过程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值