写点什么

MySQL 查询执行顺序:一张图看懂 SQL 是如何工作的

  • 2025-07-07
    福建
  • 本文字数:3360 字

    阅读完需:约 11 分钟

你写的 SQL 语句为什么这么慢?为什么有时候加了索引还是不走?为什么 GROUP BY 要放在 WHERE 后面?这些问题的答案都藏在 SQL 的执行顺序里!


🔥 开篇:一个让人困惑的问题


作为程序员,你是否遇到过这样的困惑:

-- 这个查询为什么报错?SELECT name, age, COUNT(*) as cntFROM users WHERE age > 18 AND cnt > 5GROUP BY name;
复制代码


明明逻辑很清楚:查找年龄大于 18 岁,且统计数量大于 5 的用户,为什么 MySQL 却告诉你 cnt 字段不存在?


答案就在 SQL 的执行顺序里!今天我们就来揭开这个神秘的面纱。


📋 SQL 执行顺序全景图


让我们先看一个完整的 SQL 查询语句:


SELECT DISTINCT column_name, COUNT(*)FROM table_name t1JOIN table_name2 t2 ON t1.id = t2.user_idWHERE conditionGROUP BY column_nameHAVING COUNT(*) > 1ORDER BY column_nameLIMIT 10 OFFSET 20;
复制代码


你以为 MySQL 是按照你写的顺序执行的吗?大错特错!

MySQL 的真实执行顺序是这样的:



🎯 详解每个执行步骤

第 1 步:FROM - 找到数据源

FROM users u
复制代码


MySQL 首先要知道数据从哪里来,所以第一步是确定表和给表起别名。

这一步做了什么?

  • 找到指定的表

  • 为表创建别名(如果有的话)

  • 准备读取数据

第 2 步:JOIN - 连接多张表

FROM users uJOIN orders o ON u.id = o.user_id
复制代码


如果查询涉及多张表,MySQL 会根据 JOIN 条件将它们连接起来。


常见的 JOIN 类型:

  • INNER JOIN:只返回两表都有的数据

  • LEFT JOIN:返回左表所有数据,右表没有则为 NULL

  • RIGHT JOIN:返回右表所有数据,左表没有则为 NULL


-- 示例:查询用户及其订单信息SELECT u.name, o.order_dateFROM users uLEFT JOIN orders o ON u.id = o.user_id;
复制代码


第 3 步:WHERE - 过滤不需要的行


WHERE u.age > 18 AND u.status = 'active'
复制代码


在分组之前,MySQL 会根据 WHERE 条件过滤掉不符合条件的行。


注意:WHERE 不能使用聚合函数!


-- ❌ 错误写法SELECT name, COUNT(*) as cntFROM usersWHERE COUNT(*) > 5;  -- 报错!
-- ✅ 正确写法SELECT name, COUNT(*) as cntFROM usersGROUP BY nameHAVING COUNT(*) > 5; -- 用HAVING
复制代码


第 4 步:GROUP BY - 数据分组

GROUP BY u.department, u.position
复制代码


将数据按照指定的列进行分组,为聚合函数做准备。

分组示例:

-- 按部门统计员工数量SELECT department, COUNT(*) as employee_countFROM usersWHERE status = 'active'GROUP BY department;
复制代码


第 5 步:HAVING - 过滤分组

HAVING COUNT(*) > 10
复制代码


HAVING 是对分组后的结果进行过滤,可以使用聚合函数。

WHERE vs HAVING 对比:



-- 找出订单数量超过10的用户SELECT user_id, COUNT(*) as order_countFROM ordersWHERE order_status = 'completed'  -- 先过滤已完成的订单GROUP BY user_idHAVING COUNT(*) > 10;  -- 再过滤订单数量超过10的用户
复制代码


第 6 步:SELECT - 选择要显示的列


SELECT u.name, u.age, COUNT(o.id) as order_count
复制代码


到了这一步,MySQL 才开始处理 SELECT 子句,选择要显示的列。

这就是为什么 WHERE 不能使用 SELECT 中定义的别名!

-- ❌ 错误:WHERE执行在SELECT之前SELECT name, age * 2 as double_ageFROM usersWHERE double_age > 50;  -- double_age还不存在!
-- ✅ 正确写法SELECT name, age * 2 as double_ageFROM usersWHERE age * 2 > 50;
复制代码


第 7 步:DISTINCT - 去除重复

SELECT DISTINCT departmentFROM users;
复制代码


如果使用了 DISTINCT,MySQL 会去除重复的行。


第 8 步:ORDER BY - 排序

ORDER BY u.age DESC, u.name ASC
复制代码


对最终结果进行排序。

ORDER BY 可以使用 SELECT 中的别名:

-- ✅ 正确:ORDER BY执行在SELECT之后SELECT name, age * 2 as double_ageFROM usersORDER BY double_age DESC;  -- 可以使用别名
复制代码


第 9 步:LIMIT - 限制结果数量

LIMIT 10 OFFSET 20
复制代码


最后一步,限制返回的结果数量。


💡 实战案例:执行顺序的应用


让我们通过一个实际案例来理解执行顺序:

-- 需求:查询每个部门中年龄大于25岁的员工数量,-- 只显示员工数量超过5人的部门,按员工数量降序排列
SELECT department, COUNT(*) as employee_countFROM usersWHERE age > 25GROUP BY department HAVING COUNT(*) > 5ORDER BY employee_count DESC;
复制代码


执行过程分析:

  1. FROM users - 确定数据源

  2. WHERE age > 25 - 过滤年龄大于 25 的员工

  3. GROUP BY department - 按部门分组

  4. HAVING COUNT(*) > 5 - 过滤员工数量超过 5 的部门

  5. SELECT department, COUNT(*) - 选择要显示的列

  6. ORDER BY employee_count DESC - 按员工数量降序排列


🚀 性能优化技巧


了解执行顺序后,我们可以进行一些性能优化:


1. WHERE 条件优化

-- ❌ 低效:先JOIN再过滤SELECT u.name, o.order_dateFROM users uJOIN orders o ON u.id = o.user_idWHERE u.status = 'active';
-- ✅ 高效:先过滤再JOINSELECT u.name, o.order_dateFROM (SELECT * FROM users WHERE status = 'active') uJOIN orders o ON u.id = o.user_id;
复制代码


2. 索引利用

-- 为WHERE条件创建索引CREATE INDEX idx_user_status_age ON users(status, age);
-- 查询会自动使用索引SELECT * FROM users WHERE status = 'active' AND age > 25;
复制代码


3. 避免不必要的排序

-- 如果不需要排序,不要使用ORDER BYSELECT department, COUNT(*)FROM usersGROUP BY department;-- 而不是SELECT department, COUNT(*)FROM usersGROUP BY departmentORDER BY department;  -- 不必要的排序
复制代码


🔧 常见错误及解决方案


错误 1:在 WHERE 中使用聚合函数

-- ❌ 错误SELECT department, COUNT(*) as cntFROM usersWHERE COUNT(*) > 5;
-- ✅ 正确SELECT department, COUNT(*) as cntFROM usersGROUP BY departmentHAVING COUNT(*) > 5;
复制代码


错误 2:在 WHERE 中使用 SELECT 别名

-- ❌ 错误SELECT name, salary * 12 as annual_salaryFROM employeesWHERE annual_salary > 100000;
-- ✅ 正确SELECT name, salary * 12 as annual_salaryFROM employeesWHERE salary * 12 > 100000;
复制代码


错误 3:GROUP BY 与 SELECT 不匹配

-- ❌ 错误:SELECT中的列必须在GROUP BY中,或者是聚合函数SELECT department, name, COUNT(*)FROM usersGROUP BY department;
-- ✅ 正确SELECT department, COUNT(*)FROM usersGROUP BY department;
复制代码


📊 执行顺序速记口诀


为了帮助大家记忆,我总结了一个口诀:

"从哪连什么,分组再筛选,选择去重排,最后限数量"

  • 从哪 - FROM

  • 连什么 - JOIN

  • 什么 - WHERE

  • 分组 - GROUP BY

  • 再筛选 - HAVING

  • 选择 - SELECT

  • 去重 - DISTINCT

  •  - ORDER BY

  • 最后限数量 - LIMIT


🎨 可视化理解


让我们用一个图表来直观理解:



🏆 实战练习


现在让我们做一个小练习,看看你是否真的理解了执行顺序:

-- 题目:下面这个查询的执行顺序是什么?SELECT     DISTINCT u.department,    AVG(u.salary) as avg_salaryFROM users uJOIN departments d ON u.dept_id = d.idWHERE u.status = 'active' AND d.budget > 100000GROUP BY u.departmentHAVING AVG(u.salary) > 50000ORDER BY avg_salary DESCLIMIT 5;
复制代码


答案:

  1. FROM users u - 确定主表

  2. JOIN departments d ON u.dept_id = d.id - 连接部门表

  3. WHERE u.status = 'active' AND d.budget > 100000 - 过滤活跃用户和预算充足的部门

  4. GROUP BY u.department - 按部门分组

  5. HAVING AVG(u.salary) > 50000 - 过滤平均工资超过 5 万的部门

  6. SELECT DISTINCT u.department, AVG(u.salary) - 选择部门和平均工资

  7. DISTINCT - 去重(虽然这里 GROUP BY 已经保证唯一性)

  8. ORDER BY avg_salary DESC - 按平均工资降序排列

  9. LIMIT 5 - 只取前 5 条记录


🎯 总结


理解 SQL 执行顺序的重要性:

  1. 避免语法错误 - 知道什么时候可以使用别名

  2. 优化查询性能 - 合理安排过滤条件的位置

  3. 正确使用聚合函数 - 区分 WHERE 和 HAVING 的使用场景

  4. 编写高效 SQL - 让数据库引擎更好地优化查询


记住这个执行顺序:FROM → JOIN → WHERE → GROUP BY → HAVING → SELECT → DISTINCT → ORDER BY → LIMIT


💪 写在最后


SQL 执行顺序虽然看起来复杂,但掌握了这个核心概念,你就能:

  • 写出更高效的 SQL 语句

  • 快速定位 SQL 错误

  • 更好地理解数据库的工作原理

  • 在面试中从容应对相关问题

下次写 SQL 的时候,不妨在心里默念一遍执行顺序,相信你会发现很多之前困惑的问题都迎刃而解了!


文章转载自:大毛啊

原文链接:https://www.cnblogs.com/damaoa/p/18964981

体验地址:http://www.jnpfsoft.com/?from=001YH

用户头像

还未添加个人签名 2023-06-19 加入

还未添加个人简介

评论

发布
暂无评论
MySQL查询执行顺序:一张图看懂SQL是如何工作的_MySQL_不在线第一只蜗牛_InfoQ写作社区