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 性能优化的钥匙!
218

被折叠的 条评论
为什么被折叠?



