news 2026/10/7 3:44:38

SQL执行顺序全面解析:逻辑顺序与MySQL物理执行流程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SQL执行顺序全面解析:逻辑顺序与MySQL物理执行流程

写过几年SQL的人,大概都背过一条口诀:FROM、WHERE、GROUP BY、HAVING、SELECT、DISTINCT、ORDER BY、LIMIT。可真上了线,一条看似平平无奇的查询把数据库拖到CPU爆满;走进面试间,一句“WHERE里为什么不能用SELECT别名,ORDER BY里却可以”也常常让老开发当场愣住。这其实都指向同一个问题:你背的这条顺序,到底是谁的顺序?它和MySQL真正执行的那条流水线,完全是两回事。

这篇文章想把这个事彻底讲透。它能帮你解决三类问题:一是写SQL时理解为什么某些写法报错、某些写法慢;二是排查线上慢查询时知道该从哪一步下手;三是面试时遇到执行顺序相关的问题不再心虚。我尽量用实际案例说话,少讲空理论,适合后端开发、数据分析师,以及正在准备数据库面试的同学阅读。

1. 先分清楚:逻辑执行顺序和MySQL的真实工作流程

1.1 你背的那条顺序,其实是关系代数的运算顺序

很多人不知道,教科书上那条“SQL执行顺序”并不是MySQL在物理上做事的顺序,而是逻辑运算顺序,它来自关系代数理论。SQL是一种声明式语言,你告诉数据库“我要什么数据”,至于怎么取、先做哪一步再做哪一步,是优化器说了算的。

这条逻辑顺序描述的是数据从“原始表集合”到“最终结果集”的变换过程:先确定数据源,再逐层过滤、聚合、投影、排序、截断。每一步都建立在前一步的结果之上,像一个流水线。理解这点很重要,因为后面很多“为什么”都能从这个流水线的位置找到答案。

比如WHERE字句在SELECT之前执行,所以SELECT里定义的别名,WHERE中用不了。比如GROUP BY在HAVING之前执行,所以HAVING可以对聚合结果进行过滤。这些都不是MySQL拍脑袋定的规则,而是关系代数运算的自然结果。

1.2 MySQL内部把一条SQL拆成四步

MySQL处理一条SQL,大致经过四个阶段。这是Server层的逻辑分工,和上面的逻辑执行顺序不是一个维度,但同样重要。

  • 连接器:负责鉴权、建立连接、管理连接状态。你执行mysql -u root -p登录的那一刻,就进入了这个阶段。
  • 分析器:先做词法分析,把SQL语句拆成一个个Token,再做语法分析,检查SQL是否符合语法规则。比如select * from t where id=1,会被拆成select、*、from、t、where、id、1这些Token,然后按语法规则生成语法树。如果语法有错,这里就直接报错了。
  • 优化器:这是最关键的阶段。优化器会决定使用哪个索引、多表连接用哪个表作为驱动表、子查询怎么改写、Join怎么做。它内部有成本模型,会估算各种执行计划的代价,然后选一个它认为最低的。
  • 执行器:真正调用存储引擎接口,逐行读取、判断、返回结果。执行器的操作是严格按照优化器生成的执行计划来的,而不是你写的SQL顺序。

可以这么类比:SQL是你下的菜单,写了“宫保鸡丁”,厨房里怎么切丁、先大火还是小火,是优化器决定的。有时候你写的字句顺序和厨房工艺完全不同,但最终上桌的菜得符合你的需求。

1.3 为什么搞清楚执行顺序比背口诀有用

背口诀只能应付最简单的面试题,真正有价值的是用执行顺序的思维去诊断问题。举一个我踩过的例子:某次线上一条SQL,WHERE条件里写了YEAR(create_time) = 2024,create_time上明明建有索引,但EXPLAIN一看走的是全表扫描,rows显示500万行。原因就在于WHERE阶段要对每一行先计算YEAR函数,再和2024比较,索引无法加速这种计算后的比较。

如果只背口诀,你只知道WHERE在GROUP BY前面,解释不了这个现象。而当你把“顺序”和“每一阶段如何利用索引”结合起来看,很多慢查询的根因就浮出水面了。这也是我在正文里反复强调的:执行顺序不只是死板的排列,它是SQL性能问题的定位坐标系。

2. 从FROM到JOIN:数据源是怎么被组织的

2.1 FROM子句不是简单读表

逻辑顺序的第一步是FROM,它的语义是确定数据源。但物理上MySQL要怎么读取这些表,却大有讲究。单表查询相对简单,难的是多表JOIN时谁先谁后。

多表JOIN的物理执行,MySQL最核心的是Nested-Loop Join(嵌套循环连接)及其变体。本质上是两层循环:外层循环遍历驱动表的每一行,内层循环到被驱动表里去匹配连接条件。MySQL 8.0之后,等值连接场景还引入了Hash Join,也就是把驱动表的连接字段构造成哈希表,再遍历被驱动表做哈希匹配,这种方式在大表等值连接时比嵌套循环快很多。

无论哪种方式,都有一个实战原则:小表驱动大表。因为外层循环的次数越少,整体扫描和匹配的成本越低。假设a表1000行,b表100万行,JOIN条件是a.id = b.id。如果a做驱动表,外层循环1000次,每次去b表匹配一次;反过来b做驱动表,外层循环100万次,成本天差地别。优化器一般会根据表统计信息和索引情况自动选择驱动顺序,但统计信息不准或者SQL本身写得太复杂时,它也会选错。这时候可以用STRAIGHT_JOIN强制指定驱动顺序,但不建议随便用,只有在明确知道优化器选错时才动手。

2.2 ON条件与WHERE条件的执行差异

这是执行顺序里最隐蔽也最容易出错的点。逻辑顺序上,ON在JOIN时执行,WHERE在JOIN完成之后执行。对于INNER JOIN,两种写法的结果一致,所以很多人没意识到它们有区别。但换成LEFT JOIN,区别就大了。

看一个经典例子:

-- 左连接右表的条件放在ON里 SELECT u.id, o.total FROM users u LEFT JOIN orders o ON o.user_id = u.id AND o.total > 100; -- 左连接右表的条件放在WHERE里 SELECT u.id, o.total FROM users u LEFT JOIN orders o ON o.user_id = u.id WHERE o.total > 100;

第一条SQL的意思是:先按o.user_id = u.id AND o.total > 100去匹配,匹配不上的左表行保留,右表字段填NULL。所以每个用户都会出现,只是没有达标订单的用户total显示为NULL。

第二条SQL的逻辑顺序是:先做LEFT JOIN,所有用户和订单连接后形成中间结果,然后WHERE把o.total > 100的行留下。这一步的副作用是,那些没有订单、total为NULL的用户也被过滤掉了,LEFT JOIN实际上变成了INNER JOIN的效果。

很多人踩过这个坑:明明写了LEFT JOIN,结果某些用户“凭空消失”。排查半天,发现是WHERE条件把右表字段过滤了。理解了执行顺序,这类问题一眼就能定位。

2.3 子查询也是FROM/JOIN的变体

写成FROM子查询时,MySQL在逻辑上把子查询结果当作一个派生表。但物理上它不一定会先物化整张派生表,MySQL 8.0的派生表合并优化(derived_merge)会把符合条件的子查询直接合并到外层查询里去。同样,IN子查询可能被改写为semi-join,EXISTS也可能被优化成其他连接方式。所以别一口咬定“子查询一定先执行”,优化器比你想象的会变通。

3. WHERE、GROUP BY、HAVING:过滤、分组与再过滤的边界

3.1 WHERE:先把数据规模压到最小

逻辑顺序里,WHERE在GROUP BY之前,这个看似简单的先后关系,实际上决定了整条SQL的性能天花板。WHERE的作用是逐行过滤,条件不满足的行直接丢弃,不进入后续任何阶段。

所以写SQL的第一直觉应该是:能在WHERE里过滤掉的,绝不放后面处理。比如要统计2024年每个用户的下单金额,你会先写WHERE create_time >= '2024-01-01' AND create_time < '2025-01-01',把数据先缩到2024年的订单,再去GROUP BY。如果反着来,先GROUP BY全表再在HAVING里过滤年份,那意味着要把所有历史订单全部分组一次,浪费巨大。

WHERE阶段的另一个关键点是索引。WHERE条件能不能命中索引,直接决定过滤是走索引快速定位,还是全表扫描逐行判断。常见的索引失效场景包括:对字段使用函数、隐式类型转换、LIKE前置通配符、OR连接非索引列。这些写法会让优化器在过滤阶段“使不上劲”,只能退化为全表扫描。

3.2 GROUP BY:分组后每组只留一个代表

GROUP BY的语义是把同一个分组键的行合并成一组,每组在结果里只占一行。它和聚合函数是天生一对:COUNT、SUM、AVG、MIN、MAX这些函数,本质上都是对组内多行做汇总计算。

这里有个特别容易让新手懵的点:分组之后,一个组里可能有多行,但你只能看到这一组的“代表行”,组内其他行的非聚合字段信息是不稳定的。MySQL 5.7之后默认开启ONLY_FULL_GROUP_BY模式,SELECT里的非聚合列必须出现在GROUP BY子句中,否则直接报错。这是一种保护机制,防止你拿到无意义的数据。

举个例子:

SELECT user_id, order_id, COUNT(*) FROM orders GROUP BY user_id;

在ONLY_FULL_GROUP_BY模式下,这条SQL会因为order_id既不在GROUP BY里,又不是聚合函数而报错。这是合理的:每个用户可能有多个订单,order_id该显示哪一个?MySQL并不知道。要么把order_id加入GROUP BY,要么把它改成GROUP_CONCAT(order_id)或MAX(order_id)这类聚合表达式。

3.3 HAVING:聚合后的过滤,不能替代WHERE

HAVING的逻辑位置在GROUP BY之后,它面对的不是“行”,而是“组”。所以HAVING里可以使用聚合函数,比如筛选下单次数超过5次的用户:

SELECT user_id, COUNT(*) AS cnt FROM orders GROUP BY user_id HAVING cnt > 5;

而WHERE在GROUP BY之前,面对的是原始行,根本不知道聚合结果,所以WHERE里不能写COUNT(*) > 5这样的条件。这就是“WHERE过滤行、HAVING过滤组”的执行顺序含义。

但要注意,HAVING能做的事不等于该用HAVING做。很多性能问题恰恰出在滥用HAVING上:把能在WHERE里做的条件,拿到HAVING里做。比如“只统计2024年的订单”,如果在WHERE里过滤,分组前数据就大幅缩减;放在HAVING里过滤,等于先对全表分组再丢组,白白浪费一轮聚合。正确姿势是先用WHERE把行级条件解决掉,再用HAVING处理组级条件。

顺带提一句:MySQL在GROUP BY和HAVING阶段还支持使用SELECT里的别名,这是官方文档明确提到的扩展行为,标准SQL是不允许的。这个特性方便归方便,但换到其他数据库可能会报错,我不建议把它当常规写法。

3.4 别名的秘密:WHERE不能用,ORDER BY能用

这个问题的答案,现在可以完整说清楚了。逻辑执行顺序里,SELECT在WHERE之后、在ORDER BY之前,所以:WHERE执行的时候,SELECT里定义的别名还不存在,你拿一个不存在的别名叫MySQL去过滤,它只能报Unknown column。而ORDER BY在SELECT之后执行,别名已经生成,所以可以用。这也是面试里最高频的执行顺序考点。

用SQL验证一下:

-- 这条会报错:Unknown column 'n' in 'where clause' SELECT name AS n FROM users WHERE n = '张三'; -- 这条正常执行 SELECT name AS n FROM users ORDER BY n;

MySQL在GROUP BY和HAVING里也允许用SELECT别名,这是它的非标准扩展。知道这个特性可以,但写生产SQL时尽量别依赖,否则以后迁移到PostgreSQL或Oracle,同样的SQL直接崩溃。我见过不止一次,代码从MySQL迁到其他库时,整批SQL因为别名问题要重写。

4. SELECT、DISTINCT、ORDER BY、LIMIT:收尾阶段的四个关键动作

4.1 SELECT投影:决定你最终看到哪些列

SELECT在逻辑顺序里排在GROUP BY和HAVING之后,它负责从前面处理完的数据里挑选列、生成别名、计算表达式。这个阶段看起来只是“选列”,但它对性能的影响往往被低估。

SELECT *是最典型的反面教材。它会把表中所有列都取出来,不仅加大网络传输量,还会让优化器无法使用覆盖索引,被迫回到主键索引二次回表。尤其在执行顺序靠后的阶段,前面明明过滤得很小,最后因为SELECT *把全行数据捞出来,性价比极低。正确做法是只SELECT需要的列,必要时候利用覆盖索引让整个查询走索引就结束,连回表都省了。

另外,如果SQL里涉及窗口函数,它在这个阶段的执行位置也很特殊:窗口函数在HAVING之后、SELECT投影之前执行。所以你可以对窗口函数的结果再在外层做过滤或排序,但不能在WHERE或HAVING里直接引用窗口函数计算后的列。很多人写“筛选每个分组第一名”时习惯用窗口函数,再用外层查询包一层过滤,原因就在这里。

4.2 DISTINCT:一种全局去重,本质是分组

DISTINCT的语义是去除结果集中的重复行,它在逻辑上排在SELECT之后。从关系运算的角度看,它本质上就是一种分组——对SELECT出来的全部列做分组,每组取一行。所以SELECT DISTINCT a, b FROM t和SELECT a, b FROM t GROUP BY a, b的结果是等价的,只是前者不能配合聚合函数使用。

性能方面,DISTINCT通常需要借助临时表或者索引扫描来做去重。如果去重列上有索引,MySQL可以借助索引有序性直接跳过重复值,效率高很多;如果没有索引,只能产生临时表去重,代价不小。所以如果你的业务里经常出现SELECT DISTINCT,值得考虑给相关列加索引,或者评估一下是否真的需要去重。

4.3 ORDER BY:排序的分水岭

ORDER BY是执行顺序里靠后的步骤,但它的物理成本一点都不低。MySQL排序有两种路径:一是利用索引的有序性直接按顺序读取,连排序都不用做;二是无法借助索引时,产生一次filesort,把数据放到内存排序缓冲区,装不下了就落盘到临时文件,用外部排序完成。

从EXPLAIN的Extra列能看到端倪:出现Using filesort就代表走了排序。我之前优化过一条SQL,GROUP BY和ORDER BY用的字段不一致,导致分组后还要对整个结果重新排序,Extra里同时出现Using temporary; Using filesort,数据量一大直接跑十几秒。后来调整了索引设计,让GROUP BY和ORDER BY都命中最左前缀,直接变成Using index,性能提升非常明显。

还有两个细节值得记一下。第一,MySQL 8.0移除了GROUP BY的隐式排序,这在老版本里是默认行为。很多老博客让你加ORDER BY NULL消除隐式排序的开销,8.0之后完全没必要这么做了。第二,ORDER BY RAND()这种写法会让优化器对全表每一行生成随机数再排序,数据量稍大就是灾难,慎用。

4.4 LIMIT:截取最后一段,但陷阱在偏移量

逻辑顺序上LIMIT是最后一步,它从排序后的结果中取指定行数。但物理上MySQL不一定会老老实实等到最后才动手,比如ORDER BY ... LIMIT 10这种场景,MySQL可以用优先队列只保留前10条,省去全量排序的成本,这也是ORDER BY加LIMIT时经常比不加LIMIT快的原因。

真正容易出问题的是大偏移量翻页,比如LIMIT 1000000, 20。逻辑上它要先跳过前100万行,再取20行。但物理上,如果没办法利用索引直接定位,MySQL就得把前面100万行全部读出来然后扔掉,代价极高。我见过很多后台列表页,翻到后面越来越慢,就是这个原因。

优化手段常用延迟关联:先只查主键ID的限额范围,再用主键回表查完整行。

-- 优化前:扫到第1000020行才停 SELECT * FROM orders ORDER BY id LIMIT 1000000, 20; -- 优化后:子查询先走索引拿到20个ID,再关联回原表 SELECT o.* FROM orders o JOIN ( SELECT id FROM orders ORDER BY id LIMIT 1000000, 20 ) t ON o.id = t.id;

第一条SQL在数据量大的时候可能要扫全表大部分数据,第二条的临时表部分可以完全走二级索引,回表只发生在20行上,差距往往是几十倍。

5. 从执行顺序到慢SQL优化:一次真实的排查记录

5.1 先学会看EXPLAIN输出

我会把EXPLAIN当作执行顺序的“物理快照”,它展示的正是优化器最终决定的行事路线。理解它比理解逻辑顺序更贴近实战,因为你要优化的,永远是物理执行计划。

EXPLAIN里有几个关键字段:

字段含义重点关注
type访问类型从好到差大致是:system、const、eq_ref、ref、range、index、ALL,看到ALL基本就是全表扫
key实际选用的索引NULL代表没用索引
rows预估扫描行数数量级越大,代价越高
Extra附加信息Using index代表覆盖索引;Using where代表过滤;Using filesort代表排序;Using temporary代表临时表

如果一条SQL的EXPLAIN里type是ALL,rows是几百万,Extra还挂着filesort和temporary,那这个查询基本可以断定有问题。接下来要做的,是按逻辑执行顺序逐环节找原因。

5.2 案例一:WHERE里用函数导致索引失效

那是我在订单表上排查的真实场景。表结构简化如下:

CREATE TABLE orders ( id BIGINT PRIMARY KEY, user_id INT, total DECIMAL(10,2), create_time DATETIME, KEY idx_create_time (create_time) );

线上慢查询日志捞出来一条:

SELECT id, total FROM orders WHERE YEAR(create_time) = 2024;

EXPLAIN的结果:

typekeyrowsExtra
ALLNULL5000000Using where

500万行全表扫描。原因就是YEAR函数套在索引列上,优化器无法对函数处理后的值使用B+树索引,只能在WHERE阶段逐行计算函数再比较。理想情况是让条件变成对原始列的范围比较:

SELECT id, total FROM orders WHERE create_time >= '2024-01-01 00:00:00' AND create_time < '2025-01-01 00:00:00';

优化后的EXPLAIN变成:

typekeyrowsExtra
rangeidx_create_time约80万Using index condition

虽然还需要回表,但扫描范围从500万锐减到几十万,而且走的是索引范围扫描,实际响应时间从4秒多降到0.2秒。这个案例的底层逻辑就是执行顺序里WHERE阶段能否高效利用索引,决定了整条SQL的命运。

5.3 案例二:LEFT JOIN驱动表选错

另一个案例和JOIN顺序有关。用户表users有10万行,订单表orders有500万行。需求是查出所有有效用户的订单总数:

SELECT u.id, COUNT(o.id) AS order_cnt FROM users u LEFT JOIN orders o ON o.user_id = u.id WHERE u.status = 1 GROUP BY u.id;

初始状态下orders.user_id没有索引,EXPLAIN显示orders表这一侧是type=ALL,rows=5000000。因为LEFT JOIN的语义决定了左表users是驱动方向,连接时要用user_id去右表orders里找匹配,右表没有索引就只能全表扫。

处理方式分两步:第一给orders.user_id加索引KEY idx_user_id (user_id),让右表连接时变成ref查找;第二是检查逻辑,如果业务上确实只需要有效用户有订单的统计,完全可以把LEFT JOIN改成INNER JOIN,这样优化器可以自由选择驱动表,尽量减少扫描量。

加索引之后,右表的访问类型从ALL变成了ref,rows从500万降到几百,整条SQL从分钟级降到秒级。这里的关键是:执行顺序告诉你ON发生在JOIN过程中,所以ON字段的索引状态直接影响JOIN阶段的代价,而不是等到WHERE再去弥补。

5.4 案例三:GROUP BY的隐式排序陷阱

再说一个MySQL版本变更带来的坑。老版本里,GROUP BY user_id除了分组,还会默认对分组列做一次排序。很多老开发为了避免这次排序,习惯写GROUP BY user_id ORDER BY NULL。这个习惯到了MySQL 8.0反而成了坏味道,因为8.0已经移除了GROUP BY隐式排序,你再写ORDER BY NULL,语义上等于什么都没做,但代码却给后人埋下疑惑。

我在8.0上做过验证,对一张500万行的订单表执行:

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

在user_id有索引的前提下,EXPLAIN里走的是松散索引扫描或者全索引扫描,不再出现额外的filesort。如果你在8.0上看到GROUP BY后面带着filesort,通常不是隐式排序导致的,而是因为你GROUP BY的列与SELECT里的其他函数、表达式混用,或者在GROUP BY之外还写了ORDER BY不同的列,迫使MySQL额外排序。

这也提醒我,网上很多SQL优化的老经验,一定要结合当前MySQL版本来验证,不能盲目照抄。

6. 高频面试题与常见误区速查

6.1 面试官爱问的几个执行顺序问题

我在面试后辈时,几乎必问这几个问题,每个都和这条执行顺序主线相关。

问题一:请简述一条SQL的完整处理过程。好的回答分两层:先说逻辑执行顺序(FROM、WHERE、GROUP BY、HAVING、SELECT、DISTINCT、ORDER BY、LIMIT),再说MySQL物理上的四步流程(连接管理、分析、优化、执行)。能说出“逻辑顺序是关系代数的运算顺序,物理顺序由优化器决定”的人,基本可以过关。

问题二:WHERE和HAVING的区别是什么?从执行顺序角度,WHERE在GROUP BY之前,对原始行过滤,不能使用聚合函数;HAVING在GROUP BY之后,对分组后的组过滤,可以使用聚合函数。性能上,能用WHERE过滤的条件不要放在HAVING里。

问题三:为什么WHERE里不能用SELECT别名,ORDER BY里可以?因为逻辑顺序里SELECT在WHERE之后,别名还没生成;而ORDER BY在SELECT之后,别名已经可用。这是MySQL官方文档也确认的行为。

问题四:LEFT JOIN中,ON和WHERE有什么区别?ON在JOIN过程中执行,决定怎么匹配;WHERE在JOIN完成后执行,决定保留哪些行。把右表的过滤条件写在WHERE里,会导致LEFT JOIN退化成内连接的语义,左表中未匹配的行被过滤掉。

问题五:大偏移量LIMIT为什么慢?怎么优化?因为要扫描并丢弃前面的大量行。优化常用的有延迟关联、记住上次翻页位置、或者用ID范围条件替换偏移量。

问题六:设计联合索引时,字段顺序怎么排?执行顺序思维在这里也有效:先考虑WHERE里的等值列,再考虑GROUP BY和ORDER BY的字段,最后才是范围查询列。等值列放最前面,排序、分组的字段尽量靠前,因为索引结构天然支持这些场景的有序扫描。

6.2 常见误区清单

误区实际情况典型后果
SQL从左到右执行逻辑顺序从FROM开始,物理顺序由优化器决定读SQL时判断错性能瓶颈
WHERE里能用SELECT别名逻辑顺序中SELECT在WHERE之后直接报unknown column
HAVING和WHERE可以互换作用时机和对象完全不同结果错误或性能下降
GROUP BY就是去重分组语义结合聚合函数,不只是去重对SELECT列的限制理解出错
LIMIT永远是最后一步逻辑上靠后,物理上优化器可能提前终止排序或扫描低估或高估查询代价
加索引就一定能被用到优化器会评估成本,有时候全表扫反而被选中索引加了不生效,还占空间

6.3 写SQL时的几个自查习惯

这些习惯是我这几年排查慢SQL逐步养成的,分享给大家。

第一,写SQL先想FROM。从数据源出发,先想清楚要处理的是哪几张表、连接条件是什么,再想过滤。很多新手从SELECT出发,列了一堆字段,最后才想到表还没选明白,那样写出来的SQL往往逻辑混乱。

第二,能提前收窄的绝不拖后。WHERE能过滤掉的、JOIN的ON能收窄的,全部往前放,不要让大数据量一路带着跑到最后。

第三,SELECT列要“扛得住分组”。走GROUP BY时,SELECT里的非聚合列必须要么在GROUP BY里,要么是聚合函数,否则要么报错要么结果无意义。

第四,用EXPLAIN验证而不是猜。SQL写得再漂亮,EXPLAIN出来是ALL全表扫,一样得改。所有优化结论,以执行计划为准。

第五,复杂SQL宁可拆开,别图一气呵成。拆成几步临时结果,每一步都用执行计划验证,排查时也容易定位是哪个环节慢了。

结尾:一点个人体会

这几年排查慢SQL,我的体感是大部分问题都能在“执行顺序”这四个字里找到答案。加了索引不走,多半是WHERE阶段对字段动了手脚;LEFT JOIN丢数据,多半是WHERE把右表条件过滤掉了;翻页越来越慢,多半是LIMIT偏移量太大又没有延迟关联。很多问题表面看是索引、是统计信息、是版本差异,往深了想,都是某一阶段的执行逻辑没理解透。

个人建议新同学看完这篇文章后,拿一条自己线上真正慢的SQL出来,按逻辑执行顺序一步一步走一遍,每走一步问自己:这个阶段的数据量是多大?能不能提前收窄?能不能用索引?这样练上几条,比背十遍口诀都管用。另外,我还有个习惯,写完SQL倒着读一遍,从LIMIT往前看到FROM,特别容易发现多余的分组、重复的排序和那些本可以提前的过滤条件。这个习惯不大起眼,但确实帮我挡掉过不少线上事故。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/7 3:43:57

AD18差分线设计与等长控制全攻略:从规则设置到DDR/PCIe实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/7 3:42:39

LDO工作原理与选型布局:压差、PSRR、环路稳定性及热设计实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/7 3:42:21

游戏支付平台源码实战:订单状态机、幂等设计与对账补偿

简介&#xff1a;本资源为游戏支付平台与第三方支付网关的完整源码包&#xff0c;面向游戏运营方、支付系统开发者及需要搭建充值通道的技术团队&#xff0c;可解决游戏内充值、订单网关对接与多渠道支付集成等核心问题。包内共约2000个文件&#xff0c;涵盖315个jsp页面、301个…

作者头像 李华
网站建设 2026/10/7 3:42:03

TJA1028 LIN收发器硬件设计与PCB布局避坑指南

1. 为什么TJA1028是LIN节点设计里最值得深挖的“性价比锚点”TJA1028这个芯片名字在汽车电子工程师的日常对话里&#xff0c;出现频率可能不如STM32或CAN收发器那么高&#xff0c;但它恰恰是那些真正跑在量产车门模块、座椅调节器、空调风门执行器里的“沉默执行者”。它不是炫…

作者头像 李华
网站建设 2026/10/7 3:41:48

PCB铺铜与过孔参数设置实战指南:从BGA到0402的避坑经验

1. 铺铜和过孔参数为什么值得单独拎出来讲画PCB这件事&#xff0c;原理图阶段大家水平差距不大&#xff0c;真正拉开差距的是Layout阶段那些看起来不起眼的参数。铺铜和过孔就是其中最典型的两个——它们不像走线那样直观&#xff0c;参数设错了也不会立刻报错&#xff0c;但板…

作者头像 李华
网站建设 2026/10/7 3:41:32

楼宇传输架构与企业网络交付:物理层布线、弱电间规划与链路测试指南

1. 楼宇传输架构到底在解决什么问题1.1 它和企业网络交付的关系先讲一个我自己踩过的坑。前年帮一家制造业客户做新厂区的企业网络交付&#xff0c;交换机、防火墙、无线控制器全部调通&#xff0c;VLAN划分、DHCP、路由策略都核对过好几遍&#xff0c;但产线办公区总是间歇性卡…

作者头像 李华