☰
MySQL SELECT执行顺序详解:从逻辑流程到性能优化实战
2026/10/8 9:11:11 网站建设 项目流程

我在刚学MySQL那会儿,干过一件特别丢脸的事。有一张订单表几百万行,我想统计每天各渠道的订单量,随手写了一条SQL,把日期处理函数直接塞在了WHERE里,结果跑了快两分钟还没出结果。当时我以为就是数据量大,后来别人帮忙看了下执行计划,才发现我连SQL的“执行顺序”都没搞明白——数据库并不会老老实实按我书写SQL的顺序去执行,而是有一套自己的逻辑流程,优化器还会在这个基础上再做调整。MySQL的SELECT语句执行顺序,可以说是“书写顺序”和“内部流程”两套体系叠加的结果,弄懂这两层,写SQL、看执行计划、做性能优化都会顺手非常多。这篇就把它彻底拆开讲透。不管是刚入门的开发,还是做了几年的后端,只要你在和MySQL打交道,这篇文章都值得读一读。

1. 书写顺序与逻辑顺序:SQL的双面人生

1.1 完整SELECT语句的书写骨架

先看一段标准得不能再标准的SELECT语句:

SELECT 字段, 聚合函数(字段) FROM 表1 JOIN 表2 ON 连接条件 WHERE 过滤条件 GROUP BY 分组字段 HAVING 分组后的过滤条件 ORDER BY 排序字段 LIMIT 偏移量, 行数;

这是绝大多数人写SQL时的顺序,属于“语法层面的书写顺序”。MySQL对关键字大小写不敏感,但子句之间的先后顺序是语法解析器定死的,你如果把HAVING写到WHERE前面,或者把ORDER BY塞到GROUP BY之前,直接语法报错。为什么定这么死?因为解析器要按固定规则来识别这段文本,顺序清楚,解析才不至于产生歧义。

但问题来了:很多初学者会下意识认为,数据库执行SQL也是从左往右、从上往下读。我当初就这么想的,以为先执行SELECT,然后把结果交给FROM,再一步步处理。这个理解错得非常离谱。SQL不是过程式语言,它的书写顺序只是“点菜清单”,不是“做菜流程”。数据库拿到这份清单后会自己规划一套后厨流水线,怎么切菜、怎么下锅、哪个灶台先开火,全由它自己决定。

1.2 声明式SQL:你只需要点菜,不需要下厨

要理解执行顺序,关键先想通一个概念:SQL是声明式语言,不是命令式语言。

拿吃火锅举例。你用Java写程序,相当于亲自下厨:先烧水、再切肉、然后下锅,顺序错了菜就废了,每一步都得自己控制。但写SQL就像你去餐厅点餐,你只需要对服务员说“来一份毛肚、一份虾滑”,至于后厨是先切毛肚还是先摆盘,锅底是先放花椒还是先放辣椒,不需要你操心。你关心的是结果,餐厅保证端上来的菜是你点的那个味道。

MySQL就是这个“后厨”。它有一套固定的逻辑处理路线,不管你怎么书写SQL,它都会按自己认为合理的方式去拿数据、过滤数据、分组数据、排序数据。这套路线,就是“逻辑查询处理顺序”。只要结果和你要求的一致,它内部是可以灵活调度、甚至并行处理的。

所以学习中真正要搞明白的,是后厨的流水线长什么样。书写顺序只是入口,逻辑执行顺序才是核心,再加上物理层面的执行流程,三者加起来才算完整。

2. 逻辑查询处理顺序:数据库的思维路线图

标准SQL定义了一套逻辑处理顺序,MySQL基本遵循它,只是在个别地方有实现上的微调。下面我把完整路线列出来,再逐段拆解。

逻辑步骤作用对象做的事情
1. FROM表确定数据来源,加载基表
2. JOIN/ON连接结果生成笛卡尔积并按ON条件过滤
3. WHERE行按条件逐行过滤
4. GROUP BY行集合按字段分组
5. 聚合函数组对每组做聚合计算
6. HAVING组按组条件过滤
7. SELECT列投影出需要的列和表达式
8. DISTINCT行去掉重复行
9. ORDER BY结果集按字段排序
10. LIMIT结果集截断返回行数

这张表是整个理解的基础,下面逐个环节细说。

2.1 数据来源先行:FROM与JOIN

逻辑执行的第一步,不是SELECT,而是FROM。数据库要先搞清楚“从哪拿数据”。如果FROM后面只有一张表,那很简单,先把这张表整体加载进来再说。如果是多张表JOIN,逻辑上会先把几张表做笛卡尔积——也就是每张表的每一行都和其他表的每一行组合一遍,然后通过ON条件把不匹配的组合删掉。

举个实际例子:

SELECT o.order_id, c.customer_name FROM orders o JOIN customers c ON o.customer_id = c.customer_id;

逻辑上的处理是:先把orders和customers全部行做笛卡尔积,生成一个巨大的临时集合,然后用o.customer_id = c.customer_id这个ON条件,把符合条件的组合保留下来。你没看错,“先全组合、再过滤”这个说法在逻辑上是成立的。虽然优化器在物理执行时不会真的傻到把几百万行的笛卡尔积先算出来——它通常更聪明,会直接走索引去做匹配——但逻辑上必须按这个语义来理解。这样你就明白为什么ON条件和WHERE条件不能混为一谈了:ON是先过滤连接产生的组合,WHERE是作用于最终连接结果的过滤,两者时机不同,LEFT JOIN时结果差异非常明显。

2.2 WHERE行过滤:在聚合之前先瘦身

拿到FROM和JOIN产生的结果集之后,轮到WHERE出场了。这一步做的事情非常纯粹:对结果集中的每一行,按照WHERE条件逐个判断,条件成立的留下,不成立的剔除。这是所有过滤操作里最“早”的一步,也是对整个性能影响最大的一步。

WHERE能干什么、不能干什么,当时我踩了很多坑才记牢。它只能对“原始列”做过滤,前面还没发生的操作它管不了。比如你在WHERE里写SUM(amount) > 1000,数据库直接报错,因为WHERE执行的时候,根本还没有“分组”,自然也不存在“聚合后的组内和”这种东西。再比如你在WHERE里引用SELECT别名,也是不行的,因为SELECT这个步骤排在WHERE后面,此时投影还没开始,哪来的别名给你引用?

所以从逻辑顺序上你能推出一堆结论:WHERE要放在GROUP BY之前,意味着它会先过滤掉大量不相关的行,让后面分组的数据更少,这是SQL性能好的一个重要原因。写SQL时把能够尽早滤掉数据的条件放在WHERE里,永远是正确的姿势。

2.3 GROUP BY改变粒度,聚合函数在此登场

WHERE过滤完之后,数据就进入分组环节。GROUP BY的作用是把结果集按照指定字段“归堆”:相同值的行被划到同一组。这一步之后,数据粒度就变了,从“一行一条记录”变成“一组一条记录”。

从这一步开始,聚合函数才有了计算的前提。比如AVG、SUM、COUNT、MAX、MIN这些,都是在分组后对每个组分别计算的。很多人以为聚合函数是在SELECT阶段执行的,其实不是——聚合发生在分组阶段,也就是逻辑顺序的第5步。SELECT之所以能看到聚合结果,是因为数据在进入SELECT之前就已经计算好了。

MySQL在分组这里还有一个容易忽略的细节,就是SQL_MODE里的ONLY_FULL_GROUP_BY。如果开启了严格模式,SELECT后面出现的非聚合字段,必须在GROUP BY子句里出现,否则直接报错。别觉得这限制烦人,它其实是SQL标准的一部分,目的是防止你选出“组内哪一行都说不清楚”的字段。MySQL 5.7以上的版本默认是开启这个模式的,我见过很多从5.6迁移上来的项目,第一波报错就是栽在这里。

2.4 HAVING组过滤:WHERE管行,HAVING管组

分组和聚合计算结束之后,就轮到HAVING出场了。HAVING和WHERE长得像双胞胎,但职责完全不同:WHERE过滤的是分组前的“行数据”,HAVING过滤的是分组后的“组数据”。你在WHERE里没法用的聚合函数,在HAVING里可以大胆用,比如HAVING COUNT(*) > 100,意思就是只要那些记录数超过100的组。

这里有个MySQL特有的细节,很多老手都会搞混。按标准SQL的逻辑顺序,HAVING在SELECT之前执行,所以理论上HAVING不能引用SELECT里的别名。但MySQL做了扩展,它允许HAVING中直接使用SELECT别名,比如:

SELECT dept_id, COUNT(*) AS cnt FROM employees GROUP BY dept_id HAVING cnt > 10;

这句在MySQL里能跑通。为什么?因为MySQL在实现HAVING时,对别名的引用做了一些特殊处理,它不是严格死板地在分组阶段就去解析cnt这个别名,而是会“记住”这个表达式,延后到合适时机求值。这属于MySQL的方言特性,换到其他数据库未必支持。理解逻辑顺序时按标准走,在MySQL里用的时候知道有这个便利就好。

2.5 SELECT投影、去重、排序和分页

完成了上面所有过滤后,才轮到SELECT这个“门面”。SELECT要做的是把你的投影需求具体化:从处理好的结果集中挑出需要的列,计算每一列上写的表达式、函数和运算。到这里,SELECT的别名才正式诞生。所以ORDER BY能用SELECT别名,就是因为排序发生在投影之后;WHERE不能用别名,是因为它发生在投影之前,这个区别是理解执行顺序时最直观的一条“判定依据”。

SELECT之后是DISTINCT去重。注意DISTINCT的执行顺序很靠后,它是对SELECT投影出来的最终结果做去重,所以只有当所有其他条件都处理完,剩余的重复行才会被合并。如果你想用DISTINCT去减少参与分组或排序的数据量,不好意思,做不到,时机完全不同。

ORDER BY再往后一步,对所有查询出来的行按指定字段排序。排序是整个流程里比较昂贵的操作之一,如果数据量很大,MySQL可能无法在内存里完成排序,只能使用磁盘临时文件,也就是我们常说的Using filesort。这个“filesort”不代表它真的用了文件,但确实说明排序有成本,尽量让排序字段走索引,或者在书写时就留意排序列和索引顺序匹配。

最后一步是LIMIT,取结果集中指定的行数。逻辑上它应该是所有步骤的最后一环,排完序之后截取。但物理执行时它又有自己的小动作,这个后面单独说。

3. 内部物理流程:MySQL后厨到底是怎么干活的

逻辑顺序解决的是“语义上怎么一步一步来”,但数据库真正干活的物理流程,比这要复杂得多。一条SELECT语句进门之后,会依次经过MySQL的几个核心组件,每个组件都有自己的职责。搞清楚这条流水线,比死记硬背执行顺序更有价值。

3.1 六大组件:SQL进门后的第一站

一条SELECT语句到达MySQL服务器后,走的是这么一条链路:

连接器负责建立连接、校验用户名密码、管理连接状态。这一步看似和查询无关,实际上非常影响体验,连接建立后MySQL会分配线程来处理请求,连接池、线程池的管理都在这一层。连接器完成后,就到了分析器。分析器做词法分析和语法分析,把你的SQL字符串“翻译”成数据库能理解的结构,相当于把英文菜名对应到后厨的具体食材。语法错误就是在这里被逮住的——比如你写了SELEC,或者HAVING位置放错了,分析器直接一巴掌拍回来。

分析器产出的东西叫语法树,接下来优化器要对它做进一步加工。在MySQL 8.0之前,语法树后面还挡着一个查询缓存,如果命中缓存直接返回结果,不用走后面流程。但从8.0开始,查询缓存被彻底移除了。为什么移除?因为维护它的代价太大,只要表数据有更新,对应的缓存就必须失效,在高并发写入场景下,命中率低到可怜,反而成了性能瓶颈。所以现在不用再考虑查询缓存这个环节了。

3.2 优化器在做什么:成本、索引与执行计划

分析器生成语法树之后,优化器才是那个真正决定SQL怎么跑的“总厨”。它做的事情叫“基于成本的优化”,英文缩写CBO。也就是说,它会模拟各种执行方案的代价,取它认为最便宜的那条路线来执行。

优化器干的事情非常多,我挑几个最有代表性的说。

第一是索引选择。同一条SQL如果可能用到多个索引,优化器会分别估算每个索引能过滤掉多少行,再结合回表成本,挑一个估算成本最低的。但这里有个痛点:优化器是基于统计信息来做估算的,如果表的统计信息过期了,比如你刚插入了大量数据还没跑ANALYZE TABLE,优化器可能选错索引。遇到“明明有索引却不走”的情况,多半就是估算偏差导致的。

第二是连接顺序重排。多表JOIN时,优化器会尝试不同的驱动顺序,一般原则是“小表驱动大表”,先把数据量小的表作为驱动表,再去匹配大表,这样能减少匹配次数。但小表大表不是靠肉眼看的,而是靠优化器估算的行数。这就是为什么有时候你手动调换JOIN顺序没用,因为优化器根本不听你的,它只认自己算出来的成本。

第三是条件下推。比如你和一个子查询做关联,优化器可能把外层WHERE条件“压”到子查询内部去执行,让子查询在更早阶段过滤掉无用数据。我们写的SQL很啰嗦,但优化器会帮我们“剪枝”,这也是为什么不要轻易相信“SQL写得长就慢”这个说法,最终要看执行计划。

优化器输出的是一个“执行计划”,这个计划会告诉执行器:第一步走哪个索引,第二步怎么关联,第三步怎么排序。执行计划不是只有EXPLAIN才能看,它真实存在于每次查询过程中,EXPLAIN只是把它显式打印给你看而已。

3.3 执行器与存储引擎:一条条数据怎么被取出来

执行计划生成后,就轮到执行器动手了。执行器是真正和存储引擎打交道的人。MySQL的架构里,执行器在Server层,存储引擎在底下负责具体的数据存取。

执行器拿到执行计划后,会调用存储引擎的接口来读取数据。比如计划里写的是“走idx_dept_id这个二级索引”,执行器就会让存储引擎去索引里定位第一条满足条件的记录。MySQL的索引是B+树结构,二级索引的叶子节点存的是“索引字段 + 主键值”。如果SELECT需要的列在二级索引里都有,那直接返回就好,这叫覆盖索引,不需要回表。但如果二级索引里没有你需要的字段,比如你查询了*,那就得拿着主键再去聚簇索引里找完整行数据,这个过程叫回表。

回表是性能杀手之一,尤其在数据量大的时候。一个二级索引匹配了几万条记录,每条都要回表查一次完整行,最坏情况下等于做了几万次随机IO。这也是为什么我一直强调“不要无脑SELECT *”——一旦走了覆盖索引优化,性能差距可能巨大。

执行器在取数据时也不是一条条和存储引擎来回折腾。它通常会把一批数据放到一个缓冲里,虽然server层和引擎层之间还是有一行一行的交互逻辑,但底层存储引擎内部会通过Buffer Pool把数据页缓存起来,并且有预读机制把相邻的数据页一起加载。所以实际性能取决于冷热数据、缓冲命中率等很多因素,不能光看执行计划就断言一条SQL一定快。

3.4 用EXPLAIN把内部流程透出来

理解内部流程最快的方式,就是直接让MySQL把执行计划“摊开”给你看。EXPLAIN的用法很简单:

EXPLAIN SELECT o.order_id, c.customer_name FROM orders o JOIN customers c ON o.customer_id = c.customer_id WHERE o.status = 1;

看执行计划时,我一般先看几个关键字段:type表示访问类型,从好到差大致是system、const、eq_ref、ref、range、index、ALL。看到ALL就说明是全表扫描,基本逃不掉优化了。key表示真正用到的索引,如果为NULL就说明没走索引。rows是优化器估算要扫描的行数,这个数字越小通常越好。Extra里藏着很多关键信息,比如Using index表示覆盖索引,Using temporary表示用了临时表,Using filesort表示排序没有走索引。

举个我自己做过的例子。假设有一个订单表,索引情况是idx_customer_id在customer_id上。执行下面的SQL:

EXPLAIN SELECT order_id, status FROM orders WHERE customer_id = 1001 ORDER BY create_time DESC;

执行计划里type是ref,key是idx_customer_id,看起来不错。但Extra里出现了Using filesort——因为create_time不在这个索引里,排序没法利用索引顺序,MySQL只能把查到的数据再排一次序。如果表里数据量很大,这个排序的代价会非常明显。后续优化方案就是建一个(customer_id, create_time)的联合索引,让排序也能直接走索引,Using filesort就消失了。这类问题不看执行计划,靠肉眼盯SQL几乎无法定位。

4. 常见误区与踩坑实录:执行顺序引发的那些血案

执行顺序这东西,光背会没用,得在实际SQL里把坑都踩一遍,记忆才牢固。我把自己遇到过、也帮别人排查过的高频问题整理成了几个典型案例。

4.1 WHERE里写聚合函数,MySQL为什么直接报错

错误写法:

SELECT dept_id, COUNT(*) FROM employees WHERE COUNT(*) > 10 GROUP BY dept_id;

这条SQL一执行MySQL就报错:Invalid use of group function。原因很简单,逻辑顺序里WHERE排在GROUP BY和聚合之前,WHERE执行时根本还没有分组信息,自然没法计算COUNT(*)。如果你想过滤“组记录数大于10”的组,正确写法是:

SELECT dept_id, COUNT(*) FROM employees GROUP BY dept_id HAVING COUNT(*) > 10;

很多新手第一反应是想“先把聚合算出来再过滤”,但实际上SQL的列子已经帮你想好了分工:WHERE管分组前,HAVING管分组后,两者各司其职。这个报错本身其实是在帮我们纠正对执行顺序的错误理解。

4.2 WHERE不能用的别名,HAVING和ORDER BY凭什么能用

WHERE中不能使用SELECT别名,因为SELECT还没执行,别名不存在。这一点标准SQL和MySQL是一致的。到了ORDER BY,因为逻辑顺序排在SELECT后面,所以ORDER BY里直接用别名是没问题的,这是正常逻辑。比如:

SELECT dept_id, AVG(salary) AS avg_sal FROM employees GROUP BY dept_id ORDER BY avg_sal DESC;

这条SQL能正常执行,avg_sal在ORDER BY阶段已经生成完毕。真正让人蒙圈的是MySQL允许HAVING里也用别名,比如前面说过的HAVING cnt > 10。按标准逻辑顺序,HAVING在SELECT之前,不该认识cnt,但MySQL为了易用性做了扩展。这个特性也带来一个小坑:如果MySQL在解析HAVING别名时遇到歧义,可能产生让人意外的结果。我的建议是,为了SQL的可移植性,HAVING里尽量写完整表达式,别依赖别名。

4.3 LIMIT放在最后,为什么还能提前把查询结束

按逻辑顺序,LIMIT应该是全流程的最后一环。但物理执行时,MySQL对LIMIT是有特殊优化的。还是拿订单表举例,假设你要查:

SELECT * FROM orders WHERE status = 1 ORDER BY create_time DESC LIMIT 1;

逻辑上应该是先把所有status=1的记录取出来,排序完,再取第一条。但MySQL的优化器很聪明,如果ORDER BY走的是create_time索引,它就可以从索引最大的那一条开始向后扫描,扫描到第一条status=1的记录后直接返回,根本不需要把全部记录都翻一遍。这就是优化器对逻辑顺序的“重排”——最终结果和逻辑顺序一致,但执行路径大大缩短。

这个特性也解释了为什么count(*)配合LIMIT会有一些反常现象。比如你写LIMIT 0, 1,MySQL可能一找到满足条件的第一行就收工,扫描行数远小于全表行数。理解这一点,对分析慢查询非常有帮助。

4.4 GROUP BY与DISTINCT纠缠不清的行列

DISTINCT去重的时机在SELECT之后,看似和GROUP BY没什么关系。但实际执行时,MySQL实现DISTINCT的方式通常就是“先排序或分组再取唯一值”,所以在写SELECT DISTINCT时,你有没有建立合适的索引,直接决定是走索引快速去重,还是硬生生搞一个临时表来去重。

举个例子:

SELECT DISTINCT customer_id FROM orders;

如果customer_id上有索引,MySQL可以直接索引扫描,因为B+树的索引节点本身就是按顺序排列的,扫描时相邻重复值很容易被识别并跳过,这种情况Extra里通常是Using index,效率扛扛的。如果没索引,MySQL就只能在内存或者磁盘临时表里做去重,数据一大就是灾难。

很多时候,SELECT DISTINCT和GROUP BY在去重效果上是一样的,但执行路径未必一样。我的习惯是:能通过索引消除临时表的一定要想办法,至少在查询频率高的热点SQL上要做到。否则数据量一上来,去重操作会撑爆临时表空间。

5. 把执行顺序真正用到优化上

理解了执行顺序,不拿来优化SQL等于白学。下面几个方向是我在日常工作中用得最多的,结合实际场景讲一讲。

5.1 过滤条件前移:少让数据走进后厨

执行顺序告诉我们,WHERE是最先执行的行级过滤,所以想让SQL跑得快,第一个原则就是:尽量在WHERE阶段把数据砍到最少,不要让无关数据进入后面的JOIN、GROUP BY和ORDER BY。

比如一个用户需要查询某部门下、薪资大于一定水平、按薪资倒序的员工列表。有些人写SQL时习惯先把全部员工捞出来,然后在应用层过滤,这是最大的反面教材。数据库的强项就是批量过滤,WHERE条件能下的早下,能让索引过滤的绝不全表扫描。我经常讲一句话:SQL优化的第一步不是加索引,而是把过滤条件往前挪,索引最多是“锦上添花”,过滤条件本身才是“雪中送炭”。

实际开发中还常见一个场景:统计某段时间内的订单总额。有人喜欢先把日期范围说清楚再聚合:

SELECT channel, SUM(amount) FROM orders WHERE order_date >= '2024-01-01' AND order_date < '2025-01-01' GROUP BY channel;

WHERE先砍掉大量历史数据,后面GROUP BY处理的规模会小很多。如果你反过来先GROUP BY全表再过滤日期,那等于让全表数据都参与了分组,代价完全不是一个量级。

5.2 分组统计如何避开Using temporary

GROUP BY在逻辑上要将相同值的行“归堆”,物理实现上MySQL经常借助排序或者哈希表来完成分组,如果数据量大,就会退化成临时表。排查时你会在EXPLAIN的Extra列里看到Using temporary,这是性能报警信号。

避免临时表最直接的办法,就是让GROUP BY字段走索引。如果索引本身就是按这些字段顺序排列的,MySQL扫描索引时相同字段值天然挨在一起,直接就能完成分组,完全不用额外建临时表。比如:

SELECT customer_id, COUNT(*) FROM orders GROUP BY customer_id;

customer_id上有索引,这条SQL的Extra就是Using index,非常干净。如果分组字段没有索引,MySQL就得先把数据搞到一个临时结构里做聚合,记录多的时候内存放不下还得到磁盘上建文件,慢到怀疑人生。后来我建索引的原则有一条:高频的GROUP BY字段,一定要纳入联合索引的前缀列,而且要注意字段顺序要和SQL里的分组顺序一致,否则索引就废了。

5.3 深分页为什么那么慢:LIMIT与回表

分页查询是几乎所有业务系统都离不开的,但它也是慢查询重灾区。假设你要翻到第5000页,每页20条:

SELECT * FROM orders ORDER BY create_time DESC LIMIT 100000, 20;

从执行顺序看,这条SQL要先排序,然后跳过前10万条,再返回20条。但问题不止在于排序,更在于回表:MySQL需要先把符合条件的主键列表取出来,再对每一行做回表查询完整数据。即使你在create_time上有索引,排序可以不费劲,但它依然要扫描完前10万条记录的主键,每条都回表拿完整行,这10万次回表就是深分页的元凶。

优化方式很多,我最常用的是“延迟关联”:

SELECT t.* FROM orders t JOIN ( SELECT order_id FROM orders ORDER BY create_time DESC LIMIT 100000, 20 ) tmp ON t.order_id = tmp.order_id;

子查询里只查主键,走索引覆盖,不需要回表,代价大大降低。拿到20个主键后,再和主表关联一次,只回表这20条就行。数据量一大,这个优化能带来几个数量级的提升。这就是把执行顺序和索引机制结合起来的典型用法:先最小化回表次数,再取数据。

5.4 几个让我收益不小的书写习惯

讲了一堆原理和案例,最后把我个人在实际编码中沉淀下来的几条执行顺序相关的书写习惯列一下,算是个小结。

第一,SELECT后面尽量只写需要的列,不要熟练地把SELECT *打天下。原因上面已经提了,覆盖索引能否生效,很多时候取决于SELECT列和索引列的匹配情况。多写一列,可能就把覆盖索引优势丢了,还多一次回表。

第二,WHERE阶段能过滤的都放在WHERE里,不要把过滤条件放到HAVING里写。HAVING执行时机很靠后,数据都已经分组完才过滤,前面白干了很多活。我见过有同事把status = 1这种纯行过滤条件写到HAVING里,结果分组数据量大得离谱,虽然结果对,性能差到没办法看。

第三,ORDER BY字段尽量和索引顺序匹配。逻辑顺序里排序发生在最后,物理上如果排序字段和索引前缀顺序一致,MySQL完全可以顺着索引顺序读取,省掉一次filesort。这里要注意,ORDER BY里字段的顺序和方向都要和索引定义一致,否则优化器没法直接利用索引顺序。

第四,写分页SQL时先看有没有深分页问题,LIMIT偏移量超过一两万就要考虑延迟关联方案了。数据量小的时候无所谓,等用户真翻到第几百页时再优化,往往已经迟了。

最后分享一点个人体会

这段时间反复研究执行顺序,我最大的收获不是背住那张表,而是养成了一个习惯:遇到SQL性能问题,先别急着改SQL,先跑一遍EXPLAIN看看执行计划,看type、key、rows、Extra这几个字段,把问题定位到具体环节,再针对性地做调整。很多看似“玄学”的慢查询,归根结底都是执行顺序和索引使用不匹配导致的。MySQL的执行顺序是一个很好的切入点,把它理解透了,你会发现写优化SQL不再是靠感觉,而是有章可循。后续有空我再写写JOIN优化的细节和索引设计的思路,这几个点配合执行顺序一起看,效果会更好。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询