MySQL 查询语句的执行顺序与书写顺序完全不同,理解这一点对编写高效 SQL 和排查错误至关重要。以下是完整执行顺序(含底层原理和示例):
标准执行顺序(逻辑阶段)
1. FROM 及 JOIN -- 确定数据源,生成虚拟表 VT1
2. ON -- 应用连接条件,过滤 VT1 → VT2
3. WHERE -- 行级过滤,生成 VT3
4. GROUP BY -- 分组聚合,生成 VT4
5. HAVING -- 组级过滤,生成 VT5
6. SELECT -- 选择列并计算表达式,生成 VT6
7. DISTINCT -- 去重,生成 VT7
8. ORDER BY -- 排序,生成 VT8
9. LIMIT/OFFSET -- 分页,生成最终结果 VT9
详细阶段解析(含底层原理)
1. FROM
& JOIN
- 作用:
加载所有表数据,执行CROSS JOIN
(笛卡尔积)。 - 生成表:
VT1
(所有行的组合) - 示例:
FROM users JOIN orders ON users.id = orders.user_id -- 先做笛卡尔积(users × orders)
2. ON
(连接条件)
- 作用:
过滤VT1
,仅保留满足连接条件的行(INNER JOIN
/LEFT JOIN
逻辑在此生效)。 - 生成表:
VT2
- ⚠️ 陷阱:
LEFT JOIN
中ON
条件不满足时,右表补NULL
(不影响左表数据)。
3. WHERE
- 作用:
对VT2
进行行级过滤(不包含聚合函数)。 - 生成表:
VT3
- 🚫 禁止操作:
SELECT name, SUM(sales) FROM orders WHERE SUM(sales) > 1000 -- 错误!WHERE 不能使用聚合函数 GROUP BY name;
4. GROUP BY
- 作用:
按指定列分组,计算聚合函数(如SUM()
、COUNT()
)。 - 生成表:
VT4
(每组单行,含聚合结果) - 示例:
GROUP BY department -- 按部门分组,计算每部门工资总和
5. HAVING
- 作用:
对分组后的VT4
进行组级过滤(可安全使用聚合函数)。 - 生成表:
VT5
- 正确示例:
HAVING AVG(salary) > 5000 -- 过滤平均工资超 5000 的部门
6. SELECT
- 作用:
- 选择最终输出的列
- 执行表达式计算(如
salary * 1.1
) - 为列设置别名(此时别名不可用于
WHERE
)
- 生成表:
VT6
- ⚠️ 注意:
SELECT name, salary * 12 AS annual_salary FROM employees WHERE annual_salary > 100000; -- 错误!WHERE 阶段别名未生成
7. DISTINCT
- 作用:
对VT6
去重(若存在GROUP BY
,可能已隐含去重)。 - 生成表:
VT7
8. ORDER BY
- 作用:
按指定列排序(唯一可使用SELECT
别名的地方)。 - 生成表:
VT8
(有序结果集) - 💡 原理:
排序可能消耗大量内存,对大表需谨慎。
9. LIMIT
/ OFFSET
- 作用:
从VT8
中截取指定行(分页查询的最后步骤)。 - 生成表:
VT9
(最终结果) - 性能提示:
LIMIT 1000, 10
会先跳过 1000 行(效率低),推荐用WHERE id > last_id
优化。
关键流程图解
经典示例分析
查询:每个部门工资超 6000 的员工数(仅显示超过 2 人的部门)
SELECT
department,
COUNT(*) AS emp_count -- 步骤6:选择列 & 别名
FROM employees -- 步骤1:加载表
WHERE salary > 6000 -- 步骤3:过滤行
GROUP BY department -- 步骤4:按部门分组
HAVING COUNT(*) > 2 -- 步骤5:过滤组
ORDER BY emp_count DESC -- 步骤8:按别名排序
LIMIT 10; -- 步骤9:分页
执行顺序拆解:
步骤 | 操作 | 结果 |
---|---|---|
1 | FROM employees | 加载所有员工数据 |
3 | WHERE salary > 6000 | 过滤出工资 >6000 的员工 |
4 | GROUP BY department | 按部门分组,计算每组的行数 |
5 | HAVING COUNT(*) > 2 | 过滤出员工数 >2 的部门 |
6 | SELECT department, COUNT(*) | 选择列并命名为 emp_count |
8 | ORDER BY emp_count DESC | 按员工数降序排序 |
9 | LIMIT 10 | 返回前 10 条结果 |
性能陷阱与优化建议
-
尽早过滤数据:
- 将严格条件放在
WHERE
(而非HAVING
),减少GROUP BY
处理量。 - 例:
WHERE date > '2023-01-01'
比HAVING date > '2023-01-01'
高效得多。
- 将严格条件放在
-
避免
SELECT *
:SELECT
阶段获取的列越少,内存和 I/O 开销越小。
-
谨慎使用排序:
ORDER BY
可能触发文件排序(filesort
),对大表用索引优化排序。- 例:为
ORDER BY create_time DESC
添加索引。
-
分页优化:
- 避免
LIMIT 100000, 10
(跳过 10 万行),改用:
SELECT * FROM orders WHERE id > 100000 -- 基于有序唯一键 ORDER BY id LIMIT 10;
- 避免
特殊场景执行顺序
窗口函数(Window Functions)
SELECT
name,
salary,
RANK() OVER (PARTITION BY dept ORDER BY salary DESC) AS rank -- 在 WHERE 后执行
FROM employees
WHERE status = 'active';
- 执行时机:
窗口函数在WHERE
之后、GROUP BY
之前执行(但晚于普通SELECT
表达式)。
子查询
- FROM 子查询:优先执行(作为数据源)。
- WHERE 子查询:每行过滤时执行(可能成为性能瓶颈)。
SELECT * FROM products WHERE price > (SELECT AVG(price) FROM products); -- 对每行执行一次
总结口诀
饭(FROM)后(ON)喂(WHERE)狗(GROUP BY)
还(HAVING)要(SELECT)去(DISTINCT)欧(ORDER BY)浪(LIMIT)
💡 终极忠告:
掌握执行顺序 = 掌握 SQL 性能优化的钥匙!