欢迎光临
我们一直在努力

对于MySQL:MySQL语句执行顺序的解析

开篇介绍:

hello 大家,那么在我们使用MySQL语句进行查询的时候,我们老是会碰到一些奇奇怪怪的问题,那么其实大部分都是因为MySQL语句执行顺序的原因,所以,在本篇博客中,我们就来学习一下MySQL语句的执行顺序。

一、先立总纲:语法顺序 vs 执行顺序,天差地别

我们先明确两个核心概念:语法顺序是你写 SQL 的顺序(为了人类阅读方便),执行顺序是 MySQL 实际处理 SQL 的顺序(为了计算机高效运算)

1.1 你写 SQL 的 “语法顺序”(人类阅读顺序)

这是我们日常写 SQL 的顺序,符合人类的思维习惯 —— 先想 “要什么”,再想 “从哪来”“怎么筛选”:

SELECT 列1 [AS 别名1], 列2 [AS 别名2], 聚合函数(列3) [AS 别名3]
FROM 表1 [表别名1]
[INNER/LEFT/RIGHT JOIN 表2 [表别名2] ON 连接条件]
WHERE 行筛选条件(不能用聚合函数/别名)
GROUP BY 分组列1, 分组列2
HAVING 分组筛选条件(可以用聚合函数)
DISTINCT
ORDER BY 排序列 [ASC/DESC](可以用别名)
LIMIT 跳过行数 OFFSET 取行数;

1.2 MySQL 执行的 “底层顺序”(计算机运算顺序)

这是 MySQL 真正干活的顺序,遵循 “先找数据→再筛数据→再加工数据→最后展示数据” 的逻辑,和做菜的流程一模一样:

1. FROM → 2. ON → 3. JOIN → 4. WHERE → 5. GROUP BY → 6. HAVING → 7. SELECT → 8. DISTINCT → 9. ORDER BY → 10. LIMIT/OFFSET

1.3 核心类比升级:把 SQL 执行比作 “做一桌年夜饭”

为了让你理解,我们把这个执行顺序类比成 “做一桌包含番茄炒蛋、红烧肉、清蒸鱼的年夜饭”:

执行步骤对应的 “年夜饭操作”核心作用大白话解释
1. FROM,后面跟着表名,可以在这里给表名取别名,本质上join后面跟着的表名,也是算是在from后面的,from语句会一起收了,这一点大家注意 去菜市场采购食材(番茄、鸡蛋、五花肉、鱼、葱姜蒜) 确定 “数据来源”—— 要操作哪些表 先搞清楚:做菜需要哪些原料?SQL 需要哪些表?
2. ON,后面跟着筛选条件,不过是两个甚至多个表的 挑出 “匹配” 的食材(比如 “新鲜番茄” 配 “土鸡蛋”,“带皮五花肉” 配 “冰糖”) 多表连接时,筛选 “匹配的行” 不是所有食材都能放一起,得先挑出能搭配的 —— 就像学生表和成绩表,只有 “学生 ID 相同” 的行才能匹配
3. JOIN,后面也是跟着表名,一般用于多表查询,from后面跟一个,join后面跟一个,可以在这里给表取别名 把匹配的食材放进同一个大盆(番茄 + 鸡蛋放盆 A,五花肉 + 冰糖放盆 B) 合并多表数据,生成第一张临时表 把挑好的搭配食材合并 —— 就像把学生表和成绩表的匹配行合并成 “学生 + 成绩” 的临时表
4. WHERE,后面跟着筛选条件 剔除坏食材(烂番茄扔掉、发臭的五花肉扔掉、破壳的鸡蛋扔掉) 分组前筛选 “原始行数据”,生成第二张临时表 做菜前先挑出好食材 ——SQL 里就是先删掉不符合条件的行,减少后续工作量
5. GROUP BY,这个后面是跟着字段名,表示说要按照什么字段进行分组,相同值会被分到同一组 按菜品分盆(盆 A 做番茄炒蛋,盆 B 做红烧肉,盆 C 做清蒸鱼) 按指定列分组,每组生成一行,生成第三张临时表 不同菜品要分开做 ——SQL 里就是相同分组列的行合并成一组,方便后续统计
6. HAVING,后面跟着筛选语句,分组之后呢,我们就可以对分的多个组进行筛选,只有满足我们的筛选条件的组,才有资格显示 剔除 “不够量” 的菜品(比如红烧肉的肉太少,直接放弃这道菜) 分组后筛选 “统计结果”,生成第四张临时表 做完半成品后,筛掉不合格的 ——SQL 里就是删掉聚合结果不符合条件的分组
7. SELECT,后面跟着字段什么的,其实就是指要展示什么字段,只不过这些字段是经过了前面的那一些筛选之后,才轮到了select,而在select这里,我们就可以对字段取别名,而别名是在这里会被MySQL知道,所以聪明的你就知道了,为什么我们在select取别名时不能用到where哪里,就是因为执行的先后顺序,这个大家要理解哦,那么显然的,在select后面的语句,就可以使用别名来进行操作啦,而且聪明的你肯定也就意识到,我们要是在select语句执行之前,就给取别名的话,比如在from和join那里给表去别名,那么select这里,当然也可以直接用别名啦,因为别名已经在select执行之前被MySQL执行啦 烹饪 + 装盘(番茄炒蛋炒熟装白盘,红烧肉炖烂装红盘) 选择要展示的列、计算表达式、定义别名,生成第五张临时表 把半成品做成成品,只装需要的部分 ——SQL 里就是只保留我们关心的列,计算总分、平均分
8. DISTINCT,这个是在select后面,字段前面,是将前面所筛选后的字段,也就是select之后的结果进行去重,这个大家之前也用过了,肯定不陌生 去掉重复的菜品(比如不小心做了两份番茄炒蛋,只留一份) 对 SELECT 结果去重,生成第六张临时表 避免餐桌菜品重复 ——SQL 里就是删掉完全相同的行
9. ORDER BY,这个就是排序啦,desc是降序,asc是升序,将前面执行完所得到的结果进行排序 餐桌摆盘(红烧肉放中间,番茄炒蛋放左边,清蒸鱼放右边) 对最终结果排序,生成第七张临时表 让菜品摆放更美观 ——SQL 里就是让数据按我们想要的顺序排列
10. LIMIT/OFFSET,这个就是最low的啦哈哈哈,毕竟他是负责要将多少内容给我们看,所以他肯定也就是最后执行的啦 上菜(只给客人上前两道菜,剩下的留着自己吃) 分页返回结果,生成最终展示表 客人吃不完所有菜,只上一部分 ——SQL 里就是避免一次性返回太多数据,卡死数据库

这个类比的核心是:MySQL 执行 SQL 的顺序,是 “从源头到结果” 的逐步加工过程,每一步都生成一张临时表,供下一步使用—— 就像做菜的每一步都生成一个半成品,供下一步加工。

二、逐步骤拆解:

接下来,我们对每个执行步骤进行拆解

步骤 1:FROM —— 确定数据来源,SQL 执行的 “源头”

底层原理

这是 SQL 执行的第一步,没有例外——MySQL 会先根据FROM后的表名,找到对应的表文件(存储在磁盘上),然后把表的元数据(表结构、列类型、索引信息)加载到内存,再根据需要,逐步把表的数据行加载到内存的临时区域(比如join_buffer)。

这里的关键是:MySQL 是 “按需加载” 数据,不是一次性把整张表加载到内存 —— 比如表有 100 万行,WHERE条件只需要 1 万行,MySQL 会只加载这 1 万行,节省内存。

实战示例

  • 单表查询:

    SELECT name, math FROM exam_result;

    执行FROM exam_result时,MySQL 会先找到exam_result表的元数据,然后准备加载该表的数据行(后续步骤会根据需要筛选)。

  • 多表查询:

    SELECT s.name, e.math FROM students s JOIN exam_result e ON s.id = e.student_id;

    执行FROM students s时,先加载students表的元数据和数据行;然后执行JOIN exam_result e时,再加载exam_result表的元数据和数据行 ——多表的加载顺序,会影响后续 JOIN 的效率。

  • 常见误区

    误区 1:认为FROM是 “最后执行” 的,因为写在 SQL 的第二行

    正解:FROM是第一步执行的,SQL 的语法顺序是为了人类阅读,和执行顺序无关。

    误区 2:FROM后可以写任意表名,不管表是否存在

    正解:如果FROM后的表不存在,MySQL 会直接报错Table 'xxx' doesn't exist,后续步骤都不会执行。

    性能优化技巧

  • 优先加载小表:多表 JOIN 时,MySQL 默认会把小表作为 “驱动表”(先加载的表),因为小表加载快,且能减少后续 JOIN 的次数 —— 比如FROM 小表 JOIN 大表比FROM 大表 JOIN 小表效率高。
  • 用子查询缩小数据源:如果只需要表的一部分数据,可以在FROM里用子查询先筛选,比如:

    SELECT name, math FROM (SELECT * FROM exam_result WHERE math > 60) AS t;

    子查询会先筛选出math>60的行,生成临时表t,再从t里查询 —— 比直接查大表效率高。

  • 避免加载无关表:不要JOIN不需要的表,比如查学生成绩,就不要JOIN教师表 —— 多余的表会增加内存消耗和 JOIN 时间。
  • 边缘情况:临时表和视图的处理

    • 临时表:FROM后可以跟临时表(比如CREATE TEMPORARY TABLE t …),临时表只存在于当前会话,关闭会话后自动删除,执行效率和普通表一样。
    • 视图:FROM后可以跟视图(比如CREATE VIEW v AS SELECT …),视图是 “虚拟表”,MySQL 执行时会先把视图转换成对应的 SELECT 语句,再执行 —— 视图本身不存储数据,性能和直接写 SELECT 一样。

    步骤 2:ON —— 多表连接的 “匹配筛选器”

    底层原理

    这是多表 JOIN 专属的步骤,只在有JOIN时执行。它的作用是:在两张表的数据行合并之前,先筛选出 “匹配” 的行—— 就像做菜前,先挑出能搭配的食材,避免把所有食材都混在一起。

    ON的匹配逻辑是:遍历驱动表的每一行,然后在被驱动表里找满足ON条件的行 ——只有满足条件的行,才会被保留到下一步。

    这里的关键是:ON是在JOIN合并数据之前执行的,所以能减少后续 JOIN 的数据量—— 这是提升多表查询效率的关键。

    on这个小baby,那可是我们后面讲到复合查询的常客,靠他解决笛卡尔积

    实战示例:不同 JOIN 类型下的 ON 表现

    我们用students表(左表)和exam_result表(右表)来举例,students表有一行 “学生 A” 但exam_result表没有他的成绩(即右表无匹配行)。

  • INNER JOIN(内连接):

    SELECT s.name, e.math FROM students s INNER JOIN exam_result e ON s.id = e.student_id;

    ON会筛选出s.id = e.student_id的行,无匹配的行(比如学生 A)会被直接丢弃,不会进入下一步。

  • LEFT JOIN(左连接):

    SELECT s.name, e.math FROM students s LEFT JOIN exam_result e ON s.id = e.student_id;

    ON会筛选出匹配的行,左表的无匹配行(学生 A)不会被丢弃,而是保留下来,右表的列填 NULL—— 这是左连接和内连接的核心区别。

  • RIGHT JOIN(右连接):

    SELECT s.name, e.math FROM students s RIGHT JOIN exam_result e ON s.id = e.student_id;

    和左连接相反,右表的无匹配行会被保留,左表的列填 NULL。

  • 那么关于左右内连接,我们后面会有一篇博客讲到。

    常见误区

    误区 1:ON和WHERE的作用一样,可以随便替换

    正解:内连接中,ON 和 WHERE 的效果一样;但外连接中,两者天差地别!比如左连接中,把ON s.id = e.student_id改成WHERE s.id = e.student_id,会导致左表的无匹配行被丢弃 —— 因为WHERE是在 JOIN 之后执行的,会筛选掉 NULL 值的行。

    误区 2:ON里只能写连接条件,不能写其他筛选条件

    正解:ON里可以写任何筛选条件,优先把筛选条件放在ON里,能提升效率 —— 比如:

    SELECT s.name, e.math FROM students s LEFT JOIN exam_result e ON s.id = e.student_id AND e.math > 60;

    这个语句会先筛选出math>60的成绩行,再和学生表连接 —— 比放在WHERE里效率高,因为减少了 JOIN 的数据量。

    性能优化技巧

  • ON条件里用索引列:连接条件的列(比如s.id和e.student_id)最好建索引 ——MySQL 可以用索引快速查找匹配的行,避免全表扫描。
  • 把复杂筛选条件放在ON里:外连接时,把针对右表的筛选条件放在ON里,而不是WHERE里 —— 既能筛选数据,又能保留左表的无匹配行。
  • 避免ON里用函数:比如ON SUBSTRING(s.name,1,2) = '孙',函数会导致索引失效,MySQL 只能全表扫描 —— 尽量用原始列做连接条件。
  • 边缘情况:NULL 值在 ON 中的处理

    ON条件里,NULL 和任何值的比较结果都是 NULL,不是 FALSE—— 比如ON e.math = NULL,永远不会匹配到任何行。如果要判断 NULL 值,必须用<=>(NULL 安全的等于),比如ON e.math <=> NULL—— 这样才能匹配到math为 NULL 的行。

    步骤 3:JOIN —— 合并多表数据,生成第一张临时表

    底层原理

    这一步的作用是:把ON筛选后的多表数据行,合并成一张临时表—— 就像把挑好的搭配食材放进同一个盆里,准备烹饪。

    JOIN的底层实现有三种算法,MySQL 会根据表的大小和索引情况自动选择:

  • 嵌套循环连接(NLJ):适合小表连接大表,用小表的每一行,去大表的索引里找匹配行 —— 效率最高。
  • 块嵌套循环连接(BNL):适合大表连接大表,把小表的数据分成块,加载到join_buffer里,再和大表匹配 —— 减少磁盘 IO。
  • 哈希连接(HJ):适合没有索引的大表连接,MySQL 会先把小表的数据做成哈希表,再用大表的数据匹配 ——MySQL 8.0 才支持。
  • 不管用哪种算法,JOIN的最终结果都是生成一张包含所有匹配行的临时表,这张临时表会传到下一步WHERE使用。

    实战示例:JOIN 生成的临时表长什么样

    我们用一个简单的例子来说明:

    • students表有两行:(1, '孙悟空'), (2, '唐三藏')
    • exam_result表有两行:(1, 98), (3, 60)
    • 执行SELECT s.id, s.name, e.math FROM students s LEFT JOIN exam_result e ON s.id = e.exam_id;左连接,会把左边的表都展示出来,如果没有右边相对应的,那就显示null

    ON筛选后,匹配的行是s.id=1,左表的s.id=2无匹配行;JOIN合并后生成的临时表是:

    s.ids.namee.math
    1 孙悟空 98
    2 唐三藏 NULL

    这张临时表就是下一步WHERE的输入数据。

    常见误区

    误区 1:多表 JOIN 的顺序不影响性能

    正解:JOIN 顺序影响巨大!MySQL 默认会选择 “小表驱动大表” 的顺序,但如果表很大,最好手动调整顺序 —— 比如FROM 小表A JOIN 大表B JOIN 超大表C,比反过来的顺序效率高 10 倍。

    误区 2:JOIN 越多,查询结果越全

    正解:JOIN 越多,数据量越大,效率越低,还容易产生笛卡尔积—— 比如两张各有 1000 行的表,不加ON条件直接 JOIN,会生成 1000*1000=100 万行的临时表,直接卡死数据库。

    性能优化技巧

  • 控制 JOIN 的表数量:尽量不要超过 3 张表 JOIN—— 表越多,临时表越大,性能越差。如果需要多表查询,可以拆成多个子查询。
  • 给连接列建索引:这是提升 JOIN 效率的最核心手段—— 比如s.id和e.exam_id都建主键索引,MySQL 用 NLJ 算法时,能瞬间找到匹配行。
  • 调整join_buffer_size参数:如果用 BNL 算法,join_buffer_size决定了每次能加载多少数据 —— 增大这个参数(比如设置为16M),能减少磁盘 IO 次数。
  • 边缘情况:笛卡尔积的产生和避免

    当ON条件缺失或无效时,JOIN会生成笛卡尔积—— 即左表的每一行和右表的每一行都组合一次,数据量爆炸式增长。比如:

    SELECT * FROM students s JOIN exam_result e; — 没有ON条件

    如果students有 100 行,exam_result有 100 行,会生成 100*100=10000 行的临时表 ——生产环境中,绝对要避免笛卡尔积!

    步骤 4:WHERE —— 分组前的 “数据过滤器”

    底层原理

    这一步的作用是:对JOIN生成的临时表,进行 “第一次筛选”,只保留符合条件的行—— 就像做菜前,把盆里的坏食材挑出来扔掉。

    WHERE的执行逻辑是:逐行判断—— 遍历临时表的每一行,满足WHERE条件的行保留,不满足的行直接删除,不进入下一步。

    这里的关键是:WHERE是在分组前执行的,所以它筛选的是 “原始行数据”,不是分组后的统计结果—— 这就是为什么WHERE里不能用聚合函数。

    实战示例:WHERE的筛选效果

    我们用步骤 3 生成的临时表来举例:

    s.ids.namee.math
    1 孙悟空 98
    2 唐三藏 NULL

    执行WHERE e.math > 60:

    • 第一行e.math=98>60,保留;
    • 第二行e.math=NULL,NULL>60的结果是 NULL(不是 FALSE),所以删除;

    最终生成的临时表是:

    s.ids.namee.math
    1 孙悟空 98

    常见误区

    误区 1:WHERE里可以用聚合函数

    正解:绝对不能用!因为WHERE在分组前执行,聚合函数(比如SUM、AVG)需要分组后才能计算 —— 比如:

    — 错误!会报 Invalid use of group function
    SELECT class, AVG(math) FROM exam_result WHERE AVG(math) > 80 GROUP BY class;

    正确的做法是把聚合函数移到HAVING里:

    SELECT class, AVG(math) FROM exam_result GROUP BY class HAVING AVG(math) > 80;

    误区 2:WHERE里可以用SELECT的别名

    正解:绝对不能用!因为WHERE在SELECT前执行,SELECT的别名还没定义 —— 比如:

    — 错误!会报 Unknown column '总分' in 'where clause'
    SELECT name, math + chinese + english AS 总分 FROM exam_result WHERE 总分 > 200;

    正确的做法是用原始表达式:

    SELECT name, math + chinese + english AS 总分 FROM exam_result WHERE math + chinese + english > 200;

    误区 3:WHERE里判断 NULL 用=

    正解:=对 NULL 无效,必须用IS NULL或IS NOT NULL—— 比如:

    — 错误!永远返回空结果
    SELECT name FROM students WHERE qq = NULL;
    — 正确
    SELECT name FROM students WHERE qq IS NULL;

    如果要同时判断普通值和 NULL,可以用<=>(NULL 安全的等于):

    SELECT name FROM students WHERE qq <=> NULL; — 等价于 qq IS NULL

    性能优化技巧

  • WHERE条件要 “精准”:尽量用=、>、<等高效运算符,避免用LIKE '%xxx'(百分号在开头,索引失效)、OR(多个OR会导致索引失效)—— 比如LIKE '孙%'可以用索引,LIKE '%孙'不行。
  • 优先筛选 “基数大” 的列:基数大的列(比如主键、唯一键)筛选后的数据量更少 —— 比如WHERE id = 1比WHERE name = '孙悟空'效率高,因为id是唯一的,name可能重复。
  • 用IN代替多个OR:多个OR会导致 MySQL 全表扫描,用IN可以用索引 —— 比如WHERE math IN (80,90,100)比WHERE math=80 OR math=90 OR math=100效率高。
  • 避免WHERE里用函数:比如WHERE YEAR(create_time) = 2024会导致索引失效,改成WHERE create_time >= '2024-01-01' AND create_time < '2025-01-01',就能用索引。
  • 边缘情况:WHERE在不同 SQL 类型中的表现

    • 单表查询:没有JOIN和ON,FROM之后直接执行WHERE—— 逻辑更简单,效率更高。
    • 子查询中的WHERE:子查询里的WHERE会先执行,生成小的临时表,再和主查询的表连接 —— 比如FROM (SELECT * FROM t WHERE status=1) AS sub,子查询的WHERE先筛选数据,这还是因为括号的优先级最高哦,而在括号里,又是from第一,where第二,所以你一下子就理解了吧

    步骤 5:GROUP BY —— 数据的 “分类器”

    底层原理

    这一步的作用是:把WHERE筛选后的临时表,按指定列 “分组”,相同分组列的行合并成一行—— 就像把盆里的食材按菜品分类,每类菜品单独处理。

    GROUP BY的底层实现有两种方式:

  • 排序分组:先按分组列排序,然后把相同值的行合并 —— 适合小数据量,排序后分组很直观。
  • 哈希分组:给每个分组列的值计算哈希值,相同哈希值的行放在同一个桶里 —— 适合大数据量,哈希分组的效率比排序分组高。
  • MySQL 会根据数据量自动选择分组方式 —— 数据量小时用排序分组,数据量大时用哈希分组。

    分组后,临时表的结构会发生本质变化:每组只有一行数据,后续的HAVING、SELECT只能对 “组” 进行操作,不能对单个行操作。

    实战示例:分组后的临时表长什么样

    我们用exam_result表的部分数据举例:

    nameclassmath
    孙悟空 一班 98
    唐三藏 一班 80
    猪悟能 二班 90

    执行GROUP BY class:

    • 一班的两行合并成一组,二班的一行单独成一组;
    • 分组后生成的临时表是(此时还没计算聚合函数):| class ||——-|| 一班 || 二班 |

    这张临时表会传到下一步HAVING,然后在SELECT里计算聚合函数(比如AVG(math))。

    常见误区

    误区 1:SELECT里可以用非分组列

    正解:严格模式下,绝对不能用!分组后每组只有一行,非分组列的值是不确定的 —— 比如:

    — 严格模式下报错:Expression #1 of SELECT list is not in GROUP BY clause
    SELECT name, class, AVG(math) FROM exam_result GROUP BY class;

    为什么会报错?因为一班有 “孙悟空” 和 “唐三藏” 两个名字,MySQL 不知道该选哪个 —— 宽松模式下会随机选一个,这会导致数据错误!正确的做法是:SELECT里只能出现分组列和聚合函数:

    SELECT class, AVG(math) FROM exam_result GROUP BY class;

     当然,要是我们不group by的话,那么select还是可以随心所欲滴。

    误区 2:多列分组的顺序不影响结果

    正解:影响分组的粒度!多列分组的顺序是 “先按第一列分,再按第二列分”—— 比如GROUP BY class, gender:

    • 先按class分成一班、二班;
    • 再在每个班里,按gender分成男生、女生;最终的分组粒度是 “班级 + 性别”,比单分class更细。

    误区 3:GROUP BY会自动排序

    正解:MySQL 5.7 及以前会自动排序,MySQL 8.0 及以后不会!比如GROUP BY class,MySQL 5.7 会按class升序排列,MySQL 8.0 不会 —— 如果需要排序,必须显式加ORDER BY。

    性能优化技巧

  • 先WHERE后GROUP BY:这是最核心的优化原则——WHERE先筛选掉无关数据,减少分组的数据量 —— 比如WHERE math>60 GROUP BY class比GROUP BY class HAVING math>60效率高 10 倍。
  • 给分组列建索引:如果用排序分组,分组列的索引可以让 MySQL 跳过排序步骤 —— 直接用索引的有序性分组,效率大幅提升。
  • 避免多列分组:分组列越多,分组粒度越细,临时表越大 —— 尽量用单列分组,或者把多列分组改成子查询。
  • 调整sort_buffer_size参数:如果用排序分组,增大sort_buffer_size(比如16M),能让 MySQL 在内存里完成排序,避免磁盘排序。
  • 边缘情况:隐式分组和空值分组

    • 隐式分组:如果SELECT里有聚合函数,但没有GROUP BY,MySQL 会把整个临时表当作 “一组”—— 比如SELECT AVG(math) FROM exam_result,就是计算所有学生的平均分。看到这个,聪明的你就知道了,为什么明明说聚合函数只能给组使用,但是我们之前使用的时候,明明没有group by,却还是能使用聚合函数了,因为MySQL会直接把一整个表当成一个组哦。
    • 空值分组:分组列的 NULL 值会被当作一组—— 比如GROUP BY qq,所有qq为 NULL 的学生都会被分到同一组。

    步骤 6:HAVING —— 分组后的 “结果过滤器”

    底层原理

    这一步的作用是:对GROUP BY生成的分组临时表,进行 “第二次筛选”,只保留符合条件的分组—— 就像做完半成品后,筛掉不够量的菜品。

    HAVING和WHERE的核心区别是:HAVING筛选的是 “分组后的统计结果”,WHERE筛选的是 “分组前的原始行数据”—— 所以HAVING里可以用聚合函数,WHERE不能。还是因为聚合函数只能对组使用哦,那么很显然,where肯定是比having要先执行的,where筛选之后的结果,才被拿来分组,而having则是对分组之后的组,进行筛选,这个还是很好理解的各位。

    HAVING的执行逻辑是:遍历每个分组,计算聚合函数的值,满足HAVING条件的分组保留,不满足的删除。

    实战示例:HAVING的筛选效果

    我们用步骤 5 的分组临时表举例,执行SELECT class, AVG(math) AS avg_math FROM exam_result WHERE math>60 GROUP BY class HAVING avg_math>80;:

  • WHERE math>60筛选后,保留math>60的行;
  • GROUP BY class分成一班、二班;
  • HAVING avg_math>80计算每个班的平均分,只保留平均分 > 80 的班级 —— 比如一班平均分 89,保留;二班平均分 75,删除。
  • 常见误区

    误区 1:HAVING和WHERE可以随便替换

    正解:绝对不能!两者的执行时机和筛选对象完全不同:

    特性WHEREHAVING
    执行时机 分组前 分组后
    筛选对象 原始行数据 分组统计结果
    能否用聚合函数 ❌ 不能 ✅ 可以
    效率 高(筛选后减少分组数据量) 低(分组后再筛选)
    记住:能放在WHERE里的筛选条件,绝不放在HAVING里!

    误区 2:HAVING里可以用SELECT的别名

    正解:MySQL 5.7 及以后可以,MySQL 5.6 及以前不可以!比如HAVING avg_math>80,avg_math是SELECT的别名 —— 为了兼容性,建议用聚合函数的原始写法,比如HAVING AVG(math)>80。

    误区 3:HAVING可以省略GROUP BY

    正解:可以!如果省略GROUP BY,MySQL 会把整个表当作一组,HAVING筛选的是整个表的聚合结果 —— 比如SELECT AVG(math) FROM exam_result HAVING AVG(math)>80,就是判断所有学生的平均分是否 > 80。oioi,这一点还是值得大家注意一下

    性能优化技巧

  • 把行筛选条件放在WHERE里:比如HAVING math>60是行筛选条件,应该移到WHERE里 ——WHERE math>60比HAVING math>60效率高得多。
  • 把分组筛选条件放在HAVING里:比如HAVING AVG(math)>80是分组筛选条件,必须放在HAVING里 —— 这是HAVING的本职工作。
  • 避免HAVING里用复杂函数:比如HAVING SUBSTRING(class,1,1)='一'会导致聚合结果重新计算 —— 尽量用分组列的原始值做筛选。
  • 边缘情况:HAVING在子查询中的应用

    HAVING可以用在子查询里,筛选分组后的结果 —— 比如:

    SELECT * FROM (SELECT class, AVG(math) AS avg_math FROM exam_result GROUP BY class) AS t WHERE t.avg_math>80;

    这个语句里,子查询的GROUP BY生成分组结果,主查询的WHERE筛选平均分 > 80 的班级 —— 效果和HAVING一样,但兼容性更好(所有 MySQL 版本都支持)。

    步骤 7:SELECT —— 数据的 “烹饪装盘器”

    底层原理

    这一步的作用是:从HAVING筛选后的分组临时表中,选择要展示的列、计算表达式、定义别名—— 就像把半成品做成成品,只装需要的部分。

    SELECT的执行逻辑是:

  • 列裁剪:只保留SELECT后指定的列,删除其他列 —— 减少数据传输量;
  • 表达式计算:计算SELECT里的表达式(比如math+chinese+english);
  • 别名定义:给列或表达式定义别名(比如AS 总分)—— 别名只在SELECT之后的步骤有效。
  • 这里的关键是:SELECT是在筛选和分组之后执行的,所以它只是 “展示数据”,不会改变数据的内容—— 就像装盘不会改变菜的味道,只是改变菜的呈现方式。

    实战示例:SELECT的列裁剪和表达式计算

    我们用步骤 6 的分组临时表举例,执行SELECT class AS 班级, AVG(math) AS 平均数学成绩 FROM exam_result WHERE math>60 GROUP BY class HAVING AVG(math)>80;:

  • 列裁剪:只保留class和AVG(math)两列,删除其他列;
  • 表达式计算:计算每个班的AVG(math);
  • 别名定义:把class改成 “班级”,把AVG(math)改成 “平均数学成绩”;最终生成的临时表是:| 班级 | 平均数学成绩 ||——|————–|| 一班 | 89 |
  • 常见误区

    误区 1:SELECT *效率很高

    正解:效率极低!SELECT *会返回所有列,包括不需要的列 —— 增加内存消耗和网络传输量。比如表有 20 列,你只需要 2 列,SELECT *会多传输 18 列的数据,速度慢很多。正确的做法是:只选需要的列—— 比如SELECT class, AVG(math) FROM …。

    误区 2:别名可以在WHERE/GROUP BY/HAVING里用

    正解:绝对不能!别名是SELECT定义的,SELECT在WHERE、GROUP BY、HAVING之后执行 —— 所以这些步骤无法识别别名。只有ORDER BY和LIMIT能识别别名。

    误区 3:SELECT里的表达式会改变原始数据正解:不会!SELECT里的表达式只是 “计算展示结果”,不会修改数据库里的原始数据 —— 比如SELECT math+10 FROM exam_result,只是展示数学成绩加 10 分的结果,不会把数据库里的math改成加 10 分。

    性能优化技巧

  • 避免SELECT *:只选需要的列 —— 这是提升查询效率的最简单、最有效的手段。
  • 简化SELECT里的表达式:复杂表达式会增加 CPU 消耗 —— 比如SELECT math+chinese+english FROM …,比SELECT (math+chinese)+english FROM …更简单,效率更高。
  • 用别名简化后续步骤:给复杂表达式定义别名,能让ORDER BY更简洁 —— 比如ORDER BY 平均数学成绩 DESC比ORDER BY AVG(math) DESC更易读。
  • 边缘情况:SELECT里的函数和常量

    • 函数使用:SELECT里可以用各种函数,比如SELECT UPPER(name) FROM students(把名字转成大写),SELECT NOW() FROM dual(查询当前时间)—— 函数的计算结果会作为列值展示。
    • 常量列:SELECT里可以直接写常量,比如SELECT name, 10 FROM students—— 会给每一行增加一个值为 10 的列,这在测试时很有用。

    步骤 8:DISTINCT —— 数据的 “去重器”

    底层原理

    这一步的作用是:对SELECT生成的临时表,去掉完全重复的行—— 就像去掉餐桌上重复的菜品。

    DISTINCT的底层实现有两种方式:

  • 排序去重:先按所有列排序,然后删除重复的行 —— 适合小数据量。
  • 哈希去重:给每一行计算哈希值,相同哈希值的行只保留一行 —— 适合大数据量。
  • DISTINCT的关键是:它作用于SELECT的所有列,不是单个列—— 比如SELECT DISTINCT class, gender FROM students,是对 “班级 + 性别” 的组合去重,不是只对class去重。

    实战示例:DISTINCT的去重效果

    我们用students表的部分数据举例:

    nameclassgender
    孙悟空 一班
    唐三藏 一班
    猪悟能 二班

    执行SELECT DISTINCT class, gender FROM students:

    • “一班 + 男” 出现了两次,去重后只保留一次;
    • “二班 + 男” 出现一次,保留;最终的临时表是:| class | gender ||——-|——–|| 一班 | 男 || 二班 | 男 |

    常见误区

    误区 1:DISTINCT只作用于紧跟它的列

    正解:作用于所有列!比如SELECT DISTINCT class, name FROM …,是对class和name的组合去重,不是只对class去重 —— 如果class相同但name不同,不会被去重这一点之前我也有讲过哦

    误区 2:DISTINCT和GROUP BY可以随便替换正解:单列去重可以替换,多列去重不一定!比如SELECT DISTINCT class FROM …等价于SELECT class FROM … GROUP BY class;但SELECT DISTINCT class, name FROM …等价于SELECT class, name FROM … GROUP BY class, name——GROUP BY的效率通常比DISTINCT高,因为GROUP BY可以用索引。

    误区 3:DISTINCT会自动排序

    正解:和GROUP BY一样,MySQL 5.7 及以前会排序,MySQL 8.0 及以后不会—— 如果需要排序,必须加ORDER BY。

    性能优化技巧

  • 用GROUP BY替代DISTINCT:GROUP BY可以利用索引,效率比DISTINCT高 —— 比如SELECT class FROM … GROUP BY class比SELECT DISTINCT class FROM …效率高。
  • 先WHERE后DISTINCT:WHERE先筛选掉无关数据,减少去重的数据量 —— 比如WHERE math>60 DISTINCT class比DISTINCT class HAVING math>60效率高。
  • 避免多列DISTINCT:多列去重的临时表很大,效率低 —— 尽量用单列去重,或者把多列去重改成子查询。
  • 边缘情况:DISTINCT和聚合函数的结合

    DISTINCT可以和聚合函数结合,计算去重后的统计结果 —— 比如SELECT COUNT(DISTINCT class) FROM students,是统计不同班级的数量;SELECT SUM(DISTINCT math) FROM exam_result,是计算不同数学成绩的总和。

    步骤 9:ORDER BY —— 数据的 “排序器”

    底层原理

    这一步的作用是:对DISTINCT生成的临时表,按指定列排序—— 就像给餐桌上的菜品摆盘,让它更美观。

    ORDER BY的底层实现和GROUP BY类似,有排序排序和哈希排序两种方式,但ORDER BY主要用排序排序—— 因为需要保证数据的有序性。

    ORDER BY的关键是:它是第一个可以使用SELECT别名的步骤—— 因为ORDER BY在SELECT之后执行,别名已经定义。

    实战示例:ORDER BY的排序效果

    我们用步骤 8 的临时表举例,执行SELECT class AS 班级, AVG(math) AS 平均数学成绩 FROM exam_result WHERE math>60 GROUP BY class HAVING AVG(math)>80 ORDER BY 平均数学成绩 DESC;:

    • 按 “平均数学成绩” 降序排序,平均分高的班级排在前面;
    • 如果两个班级的平均分相同,会按class升序排列(默认)。

    常见误区(新手必踩)

    误区 1:ORDER BY里不能用SELECT的别名

    正解:可以用!这是ORDER BY的专属特权 —— 比如ORDER BY 平均数学成绩 DESC比ORDER BY AVG(math) DESC更简洁、更易读。

    误区 2:ORDER BY的排序是稳定的

    正解:默认不稳定!如果排序列的值相同,MySQL 不保证这些行的相对顺序 —— 比如两个班级的平均分都是 89,第一次查询可能一班在前,第二次可能二班在前。如果需要稳定排序,可以加一个唯一列(比如id)——ORDER BY 平均数学成绩 DESC, id ASC。

    误区 3:ORDER BY对 NULL 值的处理和普通值一样

    正解:不一样!MySQL 中,NULL 被视为最小值—— 升序排序时,NULL 排在最前面;降序排序时,NULL 排在最后面。比如ORDER BY qq ASC,qq为 NULL 的学生排在最前面;ORDER BY qq DESC,qq为 NULL 的学生排在最后面。

    性能优化技巧

  • 给排序列建索引:这是提升排序效率的最核心手段—— 索引是有序的,MySQL 可以直接用索引的有序性返回数据,跳过排序步骤,效率提升 10 倍以上。
  • 避免多列排序:多列排序会增加排序的时间和内存消耗 —— 尽量用单列排序,或者把多列排序改成子查询。
  • 调整sort_buffer_size参数:增大sort_buffer_size(比如32M),能让 MySQL 在内存里完成排序,避免磁盘排序 —— 磁盘排序的效率比内存排序低 100 倍。
  • 用LIMIT限制排序的数据量:如果只需要前 10 条数据,ORDER BY … LIMIT 10会让 MySQL 只排序前 10 条数据,而不是所有数据 —— 效率大幅提升。
  • 边缘情况:ORDER BY里的函数和表达式

    ORDER BY里可以用函数和表达式 —— 比如ORDER BY UPPER(class) ASC(按班级名大写排序),ORDER BY math+chinese+english DESC(按总分降序排序)—— 但函数和表达式会导致索引失效,尽量用列名或别名排序。

    步骤 10:LIMIT/OFFSET —— 数据的 “分页器”

    底层原理

    这是 SQL 执行的最后一步,作用是:对ORDER BY生成的排序临时表,分页返回结果—— 就像只给客人上前两道菜,剩下的留着自己吃。

    LIMIT/OFFSET的语法是:LIMIT 取行数 OFFSET 跳过行数—— 比如LIMIT 3 OFFSET 0是取前 3 行,LIMIT 3 OFFSET 3是取第 4-6 行。

    LIMIT的关键是:它必须和ORDER BY一起使用—— 否则数据的顺序不确定,分页结果会随机变化。

    实战示例:LIMIT的分页效果

    我们用步骤 9 的排序临时表举例,表中有 5 个班级,按平均分降序排列:

    班级平均数学成绩
    一班 89
    三班 85
    二班 82
    五班 78
    四班 75

    执行LIMIT 2 OFFSET 0:取前 2 行,返回一班、三班;执行LIMIT 2 OFFSET 2:取第 3-4 行,返回二班、五班;执行LIMIT 2 OFFSET 4:取第 5 行,返回四班(不足 2 行,只返回 1 行)。

    常见误区(新手必踩)

    误区 1:LIMIT可以不用ORDER BY

    正解:绝对不能!没有ORDER BY,数据的顺序是不确定的 —— 比如第一次LIMIT 2返回一班、三班,第二次可能返回二班、五班,分页结果完全混乱。

    误区 2:OFFSET越大,效率越高

    正解:OFFSET越大,效率越低!比如LIMIT 2 OFFSET 100000,MySQL 需要先遍历 10 万行数据,再取 2 行 —— 这会导致查询非常慢。解决方案:游标分页—— 用唯一列(比如id)代替OFFSET,比如WHERE id > 100000 LIMIT 2——MySQL 可以用索引快速定位到id>100000的行,效率大幅提升。

    误区 3:LIMIT会修改原始数据

    正解:不会!LIMIT只是 “限制返回的行数”,不会修改数据库里的原始数据 —— 比如LIMIT 2只是返回前 2 行,不会删除后面的行。

    性能优化技巧

  • 必须搭配ORDER BY:保证分页结果的稳定性 —— 这是分页的第一原则。
  • 用游标分页替代OFFSET分页:对于大数据量的分页,游标分页是唯一高效的方式 —— 比如:

    sql

    — 第1页:id<100 LIMIT 2
    SELECT * FROM exam_result WHERE id < 100 ORDER BY id DESC LIMIT 2;
    — 第2页:id<80 LIMIT 2(假设第1页的最后一个id是80)
    SELECT * FROM exam_result WHERE id < 80 ORDER BY id DESC LIMIT 2;

  • 限制LIMIT的取行数:不要一次取太多行 —— 比如LIMIT 1000比LIMIT 10000效率高得多。
  • 边缘情况:LIMIT在无ORDER BY时的表现

    如果没有ORDER BY,LIMIT会返回 “MySQL 认为的前 N 行”—— 这通常是数据在磁盘上的存储顺序,完全随机,没有任何意义 ——生产环境中,绝对要避免这种用法!

    三、实战升华:复杂 SQL 的执行顺序拆解 

    3.1 复杂 SQL 案例:多表 JOIN + 分组 + 排序 + 分页

    我们用一个综合案例,完整拆解执行顺序,让你把前面的知识点融会贯通:

    SELECT
    s.class AS 班级,
    COUNT(DISTINCT s.id) AS 学生数,
    AVG(e.math) AS 平均数学成绩
    FROM
    students s
    LEFT JOIN
    exam_result e ON s.id = e.student_id AND e.math > 60
    WHERE
    s.gender = '男'
    GROUP BY
    s.class
    HAVING
    COUNT(DISTINCT s.id) > 5
    ORDER BY
    平均数学成绩 DESC
    LIMIT
    3 OFFSET 0;

    3.2 执行顺序完整拆解(一步一步来)

    执行步骤具体操作生成的临时表特征
    1. FROM 加载students s表的元数据和数据行 包含所有学生的原始数据
    2. ON 筛选s.id = e.student_id且e.math>60的匹配行;左表无匹配行的e.math填 NULL 包含男生 + 匹配的成绩行,以及男生 + NULL 成绩行
    3. JOIN 合并学生表和成绩表的匹配行,生成临时表 T1 T1 的列:s.id, s.name, s.class, s.gender, e.math
    4. WHERE 筛选s.gender='男'的行,生成临时表 T2 T2 只包含男生的行,排除女生
    5. GROUP BY 按s.class分组,生成临时表 T3 T3 的列:s.class;每组对应一个班级
    6. HAVING 筛选COUNT(DISTINCT s.id)>5的分组,生成临时表 T4 T4 只包含学生数 > 5 的班级
    7. SELECT 选择s.class(别名班级)、COUNT(DISTINCT s.id)(别名学生数)、AVG(e.math)(别名平均数学成绩),生成临时表 T5 T5 的列:班级,学生数,平均数学成绩
    8. DISTINCT T5 无重复行,直接生成 T6=T5 T6 和 T5 一样
    9. ORDER BY 按 “平均数学成绩” 降序排序,生成临时表 T7 T7 的班级按平均分从高到低排列
    10. LIMIT 跳过 0 行,取前 3 行,生成最终结果表 最终结果:前 3 个平均分最高的班级

    3.3 用 EXPLAIN 验证执行顺序

    EXPLAIN是 MySQL 的 “执行计划查看工具”,可以帮你验证 SQL 的执行顺序 —— 执行EXPLAIN + 你的SQL,会返回一张执行计划表,关键字段的含义如下:

    • id:执行顺序的标识 ——id 相同的步骤,按从上到下的顺序执行;id 不同的步骤,id 越大,执行顺序越靠前。
    • select_type:查询的类型 —— 比如SIMPLE(简单查询)、DERIVED(子查询)、JOIN(连接查询)。
    • table:当前步骤操作的表 —— 对应FROM和JOIN的表。
    • type:访问类型 ——ALL(全表扫描)、index(索引扫描)、range(范围扫描)、ref(索引匹配)、const(常量匹配)——type越好,效率越高。
    • Extra:额外信息 —— 比如Using index(使用索引)、Using where(使用 WHERE 筛选)、Using temporary(使用临时表)、Using filesort(使用文件排序)—— 这些信息可以帮你定位性能瓶颈。

    比如执行EXPLAIN上面的复杂 SQL,会看到:

    • table列先出现s(学生表),再出现e(成绩表)—— 对应FROM和JOIN的顺序;
    • Extra列有Using where—— 对应WHERE筛选;
    • Extra列有Using temporary—— 对应GROUP BY生成临时表;
    • Extra列有Using filesort—— 对应ORDER BY排序。

    四、终极避坑指南

    错误类型错误示例错误原因解决方案
    1. WHERE 用聚合函数 SELECT class, AVG(math) FROM t WHERE AVG(math)>80 GROUP BY class; WHERE 在分组前执行,聚合函数未计算 把聚合函数移到 HAVING:HAVING AVG(math)>80
    2. WHERE 用 SELECT 别名 SELECT math+chinese 总分 FROM t WHERE 总分>200; WHERE 在 SELECT 前执行,别名未定义 用原始表达式:WHERE math+chinese>200
    3. SELECT 用非分组列 SELECT name, class FROM t GROUP BY class; 严格模式下,非分组列的值不确定 只选分组列 + 聚合函数:SELECT class, AVG(math) FROM t GROUP BY class;
    4. HAVING 替代 WHERE SELECT class FROM t GROUP BY class HAVING math>60; HAVING 在分组后筛选,效率低 用 WHERE 先筛选:WHERE math>60 GROUP BY class;
    5. ORDER BY 不用别名 SELECT AVG(math) avg_math FROM t GROUP BY class ORDER BY AVG(math) DESC; 语法冗余,不易读 用别名排序:ORDER BY avg_math DESC;
    6. LIMIT 不用 ORDER BY SELECT * FROM t LIMIT 10; 数据顺序不确定,分页结果随机 加 ORDER BY:ORDER BY id LIMIT 10;
    7. OFFSET 分页大数据量 SELECT * FROM t ORDER BY id LIMIT 2 OFFSET 100000; OFFSET 越大,效率越低 用游标分页:WHERE id>100000 LIMIT 2;
    8. DISTINCT 多列去重 SELECT DISTINCT class, name, gender FROM t; 多列去重效率低,临时表大 用 GROUP BY 替代:SELECT class, name, gender FROM t GROUP BY class, name, gender;
    9. ON 和 WHERE 混淆(外连接) SELECT s.name, e.math FROM s LEFT JOIN e ON s.id=e.id WHERE e.math>60; WHERE 会删除左表无匹配的行 把条件移到 ON:ON s.id=e.id AND e.math>60;
    10. JOIN 无 ON 条件 SELECT * FROM s JOIN e; 生成笛卡尔积,数据量爆炸 加 ON 条件:JOIN e ON s.id=e.student_id;

    五、总结

    5.1 核心逻辑(牢记这三句话)

  • 执行顺序≠语法顺序:MySQL 执行 SQL 的顺序是FROM→ON→JOIN→WHERE→GROUP BY→HAVING→SELECT→DISTINCT→ORDER BY→LIMIT,和你写 SQL 的顺序完全相反。
  • 早筛选,晚排序:优化 SQL 的核心原则是 —— 尽量在早期步骤(FROM/ON/WHERE)减少数据量,尽量在晚期步骤(ORDER BY/LIMIT)减少操作量。
  • 临时表是关键:每一步执行都会生成一张临时表,供下一步使用 —— 理解临时表的变化,就能理解 SQL 的执行流程,那么其实也就是,MySQL中一切皆文件!
  • 最后,大家复习一下:from、on、join、where、group by、having、select、distinct、order by、limit。

    结语:从 “会写 SQL” 到 “写好 SQL”,执行顺序是你最坚实的底气

    当你看到这里,我想你大概率和曾经的我一样,在学习 MySQL 的路上有过这样的时刻:明明照着教程写的 SQL 语句,却报出 “Unknown column” 的错误;明明筛选条件写对了,结果却少了一大半数据;明明想优化慢查询,却对着几十行的 SQL 无从下手…… 而这一切的核心,答案都藏在我们今天聊透的 “执行顺序” 里。

    我始终觉得,学习 SQL 最忌讳的就是 “只记语法,不问底层”。就像做菜时只记 “放油、放菜、放盐” 的步骤,却不知道 “为什么先放油”“为什么盐要后放”,一旦换个食材、改个口味,就手忙脚乱。我们把 SQL 执行顺序比作 “做年夜饭”,不只是为了让抽象的逻辑变具象,更是想让你明白:MySQL 执行 SQL 的过程,和我们处理一件事的底层逻辑是相通的 —— 先明确来源(FROM),再筛选匹配(ON/JOIN/WHERE),再分类处理(GROUP BY/HAVING),最后包装呈现(SELECT/DISTINCT/ORDER BY/LIMIT)。每一步都不是孤立的,而是数据从 “原始原料” 到 “成品展示” 的流转,每一步生成的临时表,都是下一步的基础。

    你可能会觉得,“记熟 10 步执行顺序就够了”,但我想告诉你,这只是起点。真正的掌握,是你看到任何一条复杂 SQL 时,都能下意识地拆解它的执行流程:先看 FROM 和 JOIN 确定数据来源,再看 ON 和 WHERE 判断数据筛选的时机,接着看 GROUP BY 和 HAVING 明确分组逻辑,最后看 SELECT 到 LIMIT 知道数据如何展示。就像我们拆解那个 “多表 JOIN + 分组 + 排序 + 分页” 的案例时,每一步临时表的变化,都是数据 “被加工” 的痕迹,理解了这些痕迹,你就不会再犯 “WHERE 里用聚合函数”“LIMIT 不加 ORDER BY” 这样的低级错误 —— 因为你知道,不是语法不允许,而是执行时机不支持。

    我见过太多新手朋友,把精力花在背 “聚合函数大全”“JOIN 类型对比” 上,却忽略了 “执行顺序” 这个最核心的骨架。就像搭积木,没有骨架的支撑,再多的零件也拼不出稳定的结构。你可以忘记某个聚合函数的写法,忘记 DISTINCT 和 GROUP BY 的细微差异,但只要记住 “FROM→ON→JOIN→WHERE→GROUP BY→HAVING→SELECT→DISTINCT→ORDER BY→LIMIT” 这个核心流转逻辑,就能通过查手册、拆步骤,解决 80% 的 SQL 问题。而那些我们反复强调的 “避坑点”—— 比如外连接中 ON 和 WHERE 的区别、OFFSET 分页的效率问题、SELECT 别名的使用范围,本质上都是对执行顺序理解不到位的体现。

    当然,我也知道,理解执行顺序的过程不会一蹴而就。你可能会在第一次用 EXPLAIN 查看执行计划时,对着 “Using temporary”“Using filesort” 这些关键词一头雾水;可能会在拆解多表 JOIN 的执行步骤时,分不清临时表 T1 和 T2 的差异;可能会在优化慢查询时,不知道该把筛选条件放在 ON 还是 WHERE 里…… 但请你相信,每一次 “卡壳” 都是进步的契机。就像做菜,第一次炒番茄炒蛋会糊锅,第一次炖红烧肉会咸,但多做几次,理解了 “火候” 和 “调味时机” 的关系,就能熟能生巧。SQL 的学习也是如此,多写、多拆、多验证,把我们今天讲的执行顺序融入到每一次写 SQL 的过程中,从单表查询到多表关联,从简单分组到复杂分页,慢慢的,你会发现曾经让你头疼的报错、让你困惑的结果,都能从执行顺序里找到答案。

    更重要的是,掌握执行顺序,不只是让你 “写出能跑的 SQL”,更是让你 “写出高效的 SQL”。在生产环境中,我们面对的不是只有 7 行数据的 exam_result 表,而是几十万、几百万甚至几千万行的业务表。此时,“早筛选,晚排序” 的优化原则就显得尤为重要 —— 把能在 WHERE 里筛选的条件,绝不放到 HAVING 里;把能在 ON 里匹配的条件,绝不留给 WHERE;给分组列、排序列建索引,用游标分页替代 OFFSET 分页…… 这些优化技巧,本质上都是在顺应 MySQL 的执行顺序,让数据在流转的早期就尽可能 “瘦身”,减少后续步骤的处理压力。这也是为什么我说,理解执行顺序是从 “会写 SQL” 到 “写好 SQL” 的分水岭 —— 前者只追求 “结果对”,后者还要追求 “效率高”。

    最后,我想和你说:SQL 从来都不是一门 “死记硬背” 的语言,它是一门 “逻辑思维” 的语言。执行顺序的背后,是 MySQL 对数据处理的底层逻辑,也是我们对 “如何高效处理数据” 的思考方式。今天我们花了大量的篇幅拆解每一步的原理、误区和技巧,不是让你把这些内容背下来,而是希望你能建立起 “按执行顺序拆解 SQL” 的思维习惯。当你下次再面对一条复杂的 SQL 时,不要慌,把它拆成我们今天讲的 10 个步骤,一步一步分析数据的流转,你会发现,再复杂的 SQL,也不过是这 10 个步骤的组合。

    学习的路上没有捷径,但有方法。执行顺序就是你学习 MySQL 的 “指南针”,无论你是刚入门的新手,还是已经有一定经验的开发者,理解并吃透它,都能让你在 SQL 的学习路上少走弯路。愿你能带着这份对 “底层逻辑” 的理解,去写每一行 SQL,去解决每一个业务问题,从 “照猫画虎” 到 “融会贯通”,最终能从容应对各种复杂的查询场景,让 SQL 成为你工作中得心应手的工具。毕竟,真正的高手,从来都不是记住了多少语法,而是理解了语法背后的逻辑 —— 而这,正是我们今天聊的全部意义。

    赞(0)
    未经允许不得转载:171主机测评 » 对于MySQL:MySQL语句执行顺序的解析
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址