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

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

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

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

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

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

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

SQL执行顺序全景图

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

SELECT DISTINCT column_name, COUNT(*)
FROM table_name t1
JOIN table_name2 t2 ON t1.id = t2.user_id
WHERE condition
GROUP BY column_name
HAVING COUNT(*) > 1
ORDER BY column_name
LIMIT 10 OFFSET 20;

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

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

graph TD
A[FROM - 确定数据源] --> B[JOIN - 连接表]
B --> C[WHERE - 过滤行]
C --> D[GROUP BY - 分组]
D --> E[HAVING - 过滤分组]
E --> F[SELECT - 选择列]
F --> G[DISTINCT - 去重]
G --> H[ORDER BY - 排序]
H --> I[LIMIT - 限制结果]

详解每个执行步骤

第1步:FROM - 找到数据源

FROM users u

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

这一步做了什么?

  • 找到指定的表
  • 为表创建别名(如果有的话)
  • 准备读取数据

第2步:JOIN - 连接多张表

FROM users u
JOIN orders o ON u.id = o.user_id

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

常见的JOIN类型:

  • INNER JOIN:只返回两表都有的数据
  • LEFT JOIN:返回左表所有数据,右表没有则为NULL
  • RIGHT JOIN:返回右表所有数据,左表没有则为NULL
-- 示例:查询用户及其订单信息
SELECT u.name, o.order_date
FROM users u
LEFT 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 cnt
FROM users
WHERE COUNT(*) > 5; -- 报错! -- 正确写法
SELECT name, COUNT(*) as cnt
FROM users
GROUP BY name
HAVING COUNT(*) > 5; -- 用HAVING

第4步:GROUP BY - 数据分组

GROUP BY u.department, u.position

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

分组示例:

-- 按部门统计员工数量
SELECT department, COUNT(*) as employee_count
FROM users
WHERE status = 'active'
GROUP BY department;

第5步:HAVING - 过滤分组

HAVING COUNT(*) > 10

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

WHERE vs HAVING 对比:

条件 WHERE HAVING
执行时机 分组前 分组后
过滤对象 分组
能否使用聚合函数
-- 找出订单数量超过10的用户
SELECT user_id, COUNT(*) as order_count
FROM orders
WHERE order_status = 'completed' -- 先过滤已完成的订单
GROUP BY user_id
HAVING 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_age
FROM users
WHERE double_age > 50; -- double_age还不存在! -- 正确写法
SELECT name, age * 2 as double_age
FROM users
WHERE age * 2 > 50;

第7步:DISTINCT - 去除重复

SELECT DISTINCT department
FROM 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_age
FROM users
ORDER BY double_age DESC; -- 可以使用别名

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

LIMIT 10 OFFSET 20

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

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

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

-- 需求:查询每个部门中年龄大于25岁的员工数量,
-- 只显示员工数量超过5人的部门,按员工数量降序排列 SELECT
department,
COUNT(*) as employee_count
FROM users
WHERE age > 25
GROUP BY department
HAVING COUNT(*) > 5
ORDER 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_date
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE u.status = 'active'; -- 高效:先过滤再JOIN
SELECT u.name, o.order_date
FROM (SELECT * FROM users WHERE status = 'active') u
JOIN 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 BY
SELECT department, COUNT(*)
FROM users
GROUP BY department;
-- 而不是
SELECT department, COUNT(*)
FROM users
GROUP BY department
ORDER BY department; -- 不必要的排序

常见错误及解决方案

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

--  错误
SELECT department, COUNT(*) as cnt
FROM users
WHERE COUNT(*) > 5; -- 正确
SELECT department, COUNT(*) as cnt
FROM users
GROUP BY department
HAVING COUNT(*) > 5;

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

--  错误
SELECT name, salary * 12 as annual_salary
FROM employees
WHERE annual_salary > 100000; -- 正确
SELECT name, salary * 12 as annual_salary
FROM employees
WHERE salary * 12 > 100000;

错误3:GROUP BY与SELECT不匹配

--  错误:SELECT中的列必须在GROUP BY中,或者是聚合函数
SELECT department, name, COUNT(*)
FROM users
GROUP BY department; -- 正确
SELECT department, COUNT(*)
FROM users
GROUP BY department;

执行顺序速记口诀

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

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

  • 从哪 - FROM
  • 连什么 - JOIN
  • 什么 - WHERE
  • 分组 - GROUP BY
  • 再筛选 - HAVING
  • 选择 - SELECT
  • 去重 - DISTINCT
  • - ORDER BY
  • 最后限数量 - LIMIT

可视化理解

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

graph LR
A[原始数据表] --> B[FROM: 确定数据源]
B --> C[JOIN: 连接其他表]
C --> D[WHERE: 过滤行]
D --> E[GROUP BY: 分组]
E --> F[HAVING: 过滤分组]
F --> G[SELECT: 选择列]
G --> H[DISTINCT: 去重]
H --> I[ORDER BY: 排序]
I --> J[LIMIT: 限制数量]
J --> K[最终结果]

实战练习

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

-- 题目:下面这个查询的执行顺序是什么?
SELECT
DISTINCT u.department,
AVG(u.salary) as avg_salary
FROM users u
JOIN departments d ON u.dept_id = d.id
WHERE u.status = 'active' AND d.budget > 100000
GROUP BY u.department
HAVING AVG(u.salary) > 50000
ORDER BY avg_salary DESC
LIMIT 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的时候,不妨在心里默念一遍执行顺序,相信你会发现很多之前困惑的问题都迎刃而解了!


关注【一只划水的程序猿】,每天分享实用的编程技巧和职场经验!

如果这篇文章对你有帮助,别忘了点赞、收藏、分享给更多的小伙伴!你的支持是我继续创作的动力!

有问题欢迎在评论区留言,我会及时回复大家的疑问。让我们一起在编程的路上越走越远!

MySQL查询执行顺序:一张图看懂SQL是如何工作的的更多相关文章

  1. Nginx源码分析:3张图看懂启动及进程工作原理

    编者按:高可用架构分享及传播在架构领域具有典型意义的文章,本文由陈科在高可用架构群分享.转载请注明来自高可用架构公众号「ArchNotes」.   导读:很多工程师及架构师都希望了解及掌握高性能服务器 ...

  2. 一张图看懂 SQL 的各种 join 用法

    下图展示了 LEFT JOIN.RIGHT JOIN.INNER JOIN.OUTER JOIN 相关的 7 种用法.   具体分解如下: 1.INNER JOIN(内连接)     2.LEFT J ...

  3. 一张图看懂sql的各种join

    下图展示了 LEFT JOIN.RIGHT JOIN.INNER JOIN.OUTER JOIN 相关的 7 种用法.

  4. 一张图看懂开源许可协议,开源许可证GPL、BSD、MIT、Mozilla、Apache和LGPL的区别

    一张图看懂开源许可协议,开源许可证GPL.BSD.MIT.Mozilla.Apache和LGPL的区别 首先借用有心人士的一张相当直观清晰的图来划分各种协议:开源许可证GPL.BSD.MIT.Mozi ...

  5. FUNMVP:几张图看懂区块链技术到底是什么?(转载)

    几张图看懂区块链技术到底是什么? 本文转载自:http://www.cnblogs.com/behindman/p/8873191.html “区块链”的概念可以说是异常火爆,好像互联网金融峰会上没人 ...

  6. [转帖]两张图看懂GDT、GDTR、LDT、LDTR的关系

    两张图看懂GDT.GDTR.LDT.LDTR的关系 2018-06-09 18:13:53 Six_666A 阅读数 2044更多 分类专栏: 深入理解linux内核   转自:http://ju.o ...

  7. 一张图看懂ANSYS17.0 流体 新功能与改进

    一张图看懂ANSYS17.0 流体 新功能与改进   提交 我的留言 加载中 已留言   一张图看懂ANSYS17.0 流体 新功能与改进 原创2016-02-03ANSYS模拟在线模拟在线 模拟在线 ...

  8. 4张图看懂delphi 10生成ipa和在iPhone虚拟器上调试(教程)

    4张图看懂delphi 10生成ipa和在iPhone虚拟器上调试(教程) (2016-02-01 03:21:06) 转载▼ 标签: delphi ios delphi10 教程 编程 分类: 编程 ...

  9. 一张图看懂css的position里的relative和absolute的区别

    position有以下属性:static.inherit.fixed.absolute.relative前三个好理解好区分:static:是默认状态,没有定位,元素出现在正常的流中(忽略 top, b ...

  10. 一张图看懂Function和Object的关系及简述instanceof运算符

    我在写一篇图解prototype和__proto__的区别时,搜资料搜到了一个有意思的现象,下面这两个运算返回的结果是一样的: Function instanceof Object;//true Ob ...

随机推荐

  1. IOC容器启动及Bean生成流程

    目录 一.容器启动 IOC启动流程 重点 二.扫描并注册BeanDefination 加载并过滤资源 注册BeanDefination 三.BeanFactory后置处理 四.注册Bean后置处理器 ...

  2. Rocketmq 如何处理消息积压 ?

    一.消息积压发现 1.Console入口 A.延迟数量(Delay) 消息积压数量,即当前Topic还剩下多少消息未处理,该值越大,表示积压的消息越多 B.最后消费时间(LastConsumeTime ...

  3. Spring Cloud Bus服务总线

    一.Spring Cloud Bus是用轻量的消息代理将分布式的节点连接起来,可以用于广播配置文件的更改或者服务的监控管理,也就是消息总线可以为微服务做监控,也可以实现应用程序之间相互通信,Sprin ...

  4. 🎀SQL注入拦截工具-动态order by

    简介 业务场景经常会存在动态order by 入参情况,在处理动态 order by 参数时,需要防止SQL注入攻击.SQL注入是一种常见的安全漏洞,攻击者可以通过这种手段操纵查询来执行恶意代码. 措 ...

  5. infiniswap安装

    环境:ubuntu14.04,内核4.04 uname -a Linux ubuntu 4.4.0-142-generic #168~14.04.1-Ubuntu SMP Sat Jan 19 11: ...

  6. NOIP集训 P11071 「QMSOI R1」 Distorted Fate 题解

    对本题的评价:有思维含量的线段树好题.曲子好听,曲绘好看,曲师人品好,谱子写得好,鸠好看 题解: P11071 「QMSOI R1」 Distorted Fate 给定一个长度为 \(n\) 的数组 ...

  7. 【SQL周周练】一句 SQL 如何帮助 5 个人买到电影院最好的座位?

    大家好,我是"蒋点数分",多年以来一直从事数据分析工作.从今天开始,与大家持续分享关于数据分析的学习内容. 本文是第 3 篇,也是[SQL 周周练]系列的第 3 篇.该系列是挑选或 ...

  8. 【记录】Samba|Windows 11的Samba连接切换用户

    Samba是一个用于共享文件和打印机的网络协议,可以使不同的操作系统之间共享文件和资源变得容易.在Windows 11上,可以使用Samba来连接到网络共享. 如果您想在Windows 11上切换用户 ...

  9. java代码中启动exe程序最简单的方法

    使用awt的Desktop类的open方法: public static void startExe(String exePath){ try { if(StringUtils.isNotBlank( ...

  10. 关于I/O与并发

    前言 由于笔者在之前发布的一文玩转NGINX中提到过I/O复用模型,在此另起一篇文章简述相关技术. 什么是I/O I/O输入/输出(Input/Output),分为IO设备和IO接口两个部分. 在PO ...