explain 看起来没问题,SQL 还是很慢,下一步该查执行、等待还是连接池?
explain 看着正常,不等于 SQL 在线上就真的没问题。先把时间花在哪里拆开,往往比继续争论索引对不对更有用:卡在拿连接、库内执行、数据库等待,还是返回后又被应用放大了。
这类 SQL 排查我见得最多的一种卡法,是 explain 已经看过,索引也不像完全走歪,可接口还是慢,团队一下子就僵在原地。
type、key、rows都不算离谱- 索引看起来也走上了
- 但线上 RT 还是高,接口还是慢
- 团队内部开始争论,到底该继续查数据库,还是转去查应用
这类场景最容易出现的误判是:把 explain 正常,当成 SQL 一定没问题。
更接近真实现场的说法应该是:explain 只能说明访问路径没有明显走歪,但它回答不了运行时的很多关键问题,比如锁等待、长事务、连接占用、高峰期路况变化,以及数据库返回后应用自己又把整体耗时放大了多少。
所以这篇不再重复 Explain 字段本身,而是专门处理另一个更容易卡人的判断:访问路径看着还行时,慢到底慢在执行、等待,还是连接池和应用侧感知。
explain 看起来没问题,但 SQL 还是很慢。下一步最值得查的是执行成本、等待成本,还是连接池放大的感知?
如果你现在连慢点是不是落在数据库都还没坐实,只是先看到接口 RT 高,那先把影响面和数据库阶段耗时对上,再回来拆这几段时间。
下面这些现场,先从这篇下手
如果你碰到的是下面这些现场,就先从这里拆:
- 你已经看到某条 SQL 或某段数据库阶段耗时偏高
explain看起来正常,但高峰期还是慢- 慢的不只是单条 SQL 文本本身,而是整段数据库停留时间
- 团队已经在争论“到底是数据库慢,还是连接池 / 应用把数据库问题放大了”
如果你现在连影响面都还没分清,比如只是“某个接口慢了”,先回到影响面判断那条线;如果你最先看到的是获取连接时间变长,就直接接 连接池等待时间变长时,如何判断是数据库慢还是应用拿着不放?。
第一轮先把慢点分型,别继续只盯 explain
如果你现在已经拿着 explain 在看,我建议先暂停一下,把这 4 段时间补齐:拿连接耗时、数据库内部执行时间、数据库等待时间、结果返回后应用处理时间。为什么我要先拆这 4 段?因为 explain 只解释访问路径,不解释运行时等待,更不解释数据库返回以后应用自己又把总耗时放大了多少。时间不先拆开,后面再看索引也很容易越看越虚。
先不要急着再刷一遍执行计划,先把你现在看到的现象放进下面这张表里。
| 你现在看到的现象 | 更像哪一类 | 下一步先查什么 |
|---|---|---|
| 平峰也慢,数据量一大就更差,返回行数和排序开销都比较重 | 执行成本偏高 | 先核对下面“执行慢”这一段里的结果集、参数分布和返回后处理 |
| 高峰更容易慢,写请求更敏感,RT 抖动大于均值抬高 | 等待成本偏高 | 先核对下面“等待慢”这一段里的锁、事务和高峰期等待 |
| 获取连接先变慢,随后接口 RT 和线程排队一起变差 | 连接池放大感知 | 先去核对连接为什么回不来 |
| 单接口慢得明显,其他接口基本正常 | 更像局部链路问题 | 先回头确认影响面,再决定要不要继续查数据库 |
| 数据库不高负载,但 active sessions、连接占用、排队感一起抬高 | 仍在数据库等待链 | 先看等待链,再决定是不是锁或事务 |
| SQL 执行时间不算夸张,但接口整体很慢 | 可能是数据库返回后又被应用放大 | 同时核对连接持有和应用处理 |
这一步的意义是:先把“慢”拆成类型,而不是继续把所有问题都塞回 Explain 里。
先把“SQL 很慢”拆成四段时间
排查 explain 正常但 SQL 仍慢,最值钱的动作不是继续抠某个字段,而是先把总耗时拆开。
至少先区分下面四段:
- 拿连接之前:线程是不是先卡在连接池里
- 进入数据库后:SQL 真正在执行什么
- 数据库内部等待:是不是在等锁、等事务、等资源路况
- 数据库返回之后:结果集处理、对象组装、事务内其他逻辑有没有继续放大
如果这四段不拆开,你看到的“SQL 很慢”很可能只是一个总感知,真实问题却已经分叉成三条完全不同的线:
- SQL 本身执行成本高
- SQL 跑得不算离谱,但等待时间更长
- 数据库阶段只是起点,后面被连接池和应用继续放大
如果更像执行慢,先核对哪几件事
当 explain 正常但平峰也慢、数据量一大就更差,这类问题通常更接近执行成本本身。
1. 看返回结果是不是比接口真实需要的更重
常见问题包括:
- 本来只需要一页数据,却返回了更大的结果集
select *把大字段一起带回- 排序字段和过滤字段没形成更稳定的访问路径
- 深分页、回表、临时表把单次成本悄悄拉高
这类问题 explain 可能看起来“不算差”,但真实耗时依然会被结果集规模和返回路径放大。
2. 看慢是不是强依赖参数分布
如果只有某类参数、某个租户、某种时间区间更慢,说明问题不一定是“整条 SQL 设计错了”,而可能是这类请求命中了更坏的数据分布。
这时要回到具体场景看:
- 是不是大页码或大范围查询更慢
- 是不是某批热点数据导致扫描范围变大
- 是不是单接口特有的查询模式在放大问题
如果你还没确认这是不是单接口独有问题,可以先回到 单接口变慢和整个服务变慢,排查入口为什么完全不同?。
3. 看数据库返回后,应用是不是又把耗时接着放大
有些现场会把这类问题也统称成“SQL 慢”,但真实情况可能是:
- 数据库阶段只占其中一部分
- 返回结果以后还有重对象组装
- 事务里还有循环处理、二次查询或序列化
如果这一层明显成立,就不要继续只盯 SQL 文本本身,而要连同连接持有时间一起看。
如果更像等待慢,优先查什么
当 explain 正常、高峰明显更差、写请求或事务型接口更敏感时,更应该先查等待,而不是继续证明访问路径没问题。
1. 先看是不是锁等待或事务竞争
这类场景最常见的特征是:
- 写接口比纯读接口更容易慢
- 同一批业务键值在高峰时更容易出问题
- RT 抬高和并发写入、状态流转同时出现
- 数据库 CPU 不一定高,但停留时间明显拉长
Explain 在这里经常“看着没问题”,因为真正慢掉的不是路径,而是等待别人释放锁、提交事务、归还资源。
2. 再看是不是长事务把等待整体包了进去
很多团队会盯着 SQL 语句本身,却忽略更本质的一层:事务边界太大。
只要事务里还有额外业务逻辑,即便 SQL 本身不算特别慢,也会表现成:
- 锁持有时间更长
- 连接归还更慢
- 后续请求开始排队
- 团队统一感知成“数据库又慢了”
如果你已经看到连接池也开始紧,下一步别只留在 SQL 这条线上,要一起接 连接池等待时间变长时,如何判断是数据库慢还是应用拿着不放?。
3. 再看是不是高峰期路况变化,而不是静态计划问题
Explain 给的是路线,线上高峰给的是路况。
你需要额外确认:
- 问题是不是只在高峰或批任务时间窗出现
- active sessions、连接占用、接口排队是否同一时间段一起抬高
- 数据库没打满,但等待型信号是不是已经先坏了
如果这些都成立,更说明你仍然在数据库等待链里,而不是已经可以把数据库排除了。这个判断边界,正是 数据库没打满,为什么 API 和连接池已经开始变慢? 那篇文章重点想帮你切清的地方。
什么情况下该从 SQL 线切回连接池或应用侧
这一步很重要,因为 explain 正常的现场里,最容易出现“数据库和应用互相甩锅”。
1. 获取连接先变慢
如果顺序是:
- 获取连接耗时先抬高
- 活跃连接接近上限
- 接口 RT 和线程排队随后变差
那就别继续只在 SQL 语句上打转了。你更需要先回答:连接为什么回不来。
2. SQL 执行不算夸张,但连接持有很久
如果 SQL 时间和事务时间不成比例,说明连接可能被应用拿去做了数据库之外的事,比如:
- 事务里调 RPC / HTTP
- 事务里做循环计算或对象处理
- 事务里放了不该放的下游等待
这时更适合转去看连接池那条线,而不是继续只看 Explain。
3. 只有一个接口或一类参数慢
这说明问题可能还没有广泛到整个数据库链路,而更像局部路径、数据特征或业务参数。先回头确认影响面,更能避免过度泛化。
最容易误判的几个地方
误判 1:explain 正常,数据库就没问题
不成立。Explain 只能排掉最表层的访问路径异常,不能排掉锁等待、事务持有、连接占用和运行时路况变化。
误判 2:只要走了索引,就不该慢
也不成立。走了索引,也可能慢在等待、回表、排序、结果集过大,或者数据库返回后应用处理过重。
误判 3:既然 explain 正常,那就一定是应用问题
也不成立。很多问题依然在数据库等待链里,只是已经不再是“没走索引”这种最表层症状。
误判 4:只看 SQL 文本,不看时间结构
真正更稳的判断,需要一起看:获取连接、SQL 执行、事务时长、连接持有和接口总 RT。只盯其中一项,很容易错判。
FAQ
1. explain 正常,是不是说明索引已经没问题了?
不是。它只能说明优化器当前选择的路径没有明显走歪,不能说明真实运行时一定没有锁等待、长事务或返回成本问题。
2. explain 正常但高峰期还是慢,优先查什么?
优先查等待链,尤其是锁等待、事务竞争、active sessions、连接占用和高峰期路况变化,而不是再做一轮静态字段解释。
3. 什么时候应该从这篇转去看连接池那条线?
当你已经看到获取连接变慢、活跃连接上升、连接归还变慢,或者 SQL 时间不算离谱但事务和连接持有明显更长时,就该切过去。
4. 如果我还不确定是不是数据库方向,应该回哪篇?
优先回 数据库没打满,为什么 API 和连接池已经开始变慢?。如果连影响面都还没切清,再往上回 单接口变慢和整个服务变慢,排查入口为什么完全不同? 或 接口响应慢怎么排查?后端 API 变慢与超时的定位步骤。这篇更适合放在你已经确认问题还留在数据库链路里之后再接着看,别把前面的分流判断和库内细查混在一起。
如果你现在更缺这类判断
如果你顺着这篇往下排,通常会接到下面几种相邻问题:
- 还没建立接口慢的总排查顺序:
接口响应慢怎么排查?后端 API 变慢与超时的定位步骤 - 还需要先分清是单接口慢,还是整个服务都慢:
单接口变慢和整个服务变慢,排查入口为什么完全不同? - 数据库没打满,但 API 和连接池已经先变慢:
数据库没打满,为什么 API 和连接池已经开始变慢? - 获取连接时间先变差,还要判断是数据库慢还是应用持有过久:
连接池等待时间变长时,如何判断是数据库慢还是应用拿着不放?
最后一句收束
遇到 explain 正常但 SQL 还是慢,不要继续在静态执行计划里兜圈子。更稳的顺序是:先拆拿连接、数据库执行、数据库等待、返回后处理四段时间,再决定继续查执行成本、等待成本,还是连接池和应用持有连接这条线。