西西软件园多重安全检测下载网站、值得信赖的软件下载站!
软件
软件
文章
搜索

首页 → 业内动态 → 业内资讯 → 索引建了却没走:我用EXPLAIN抓出那条跑了40秒的慢SQL

索引建了却没走:我用EXPLAIN抓出那条跑了40秒的慢SQL

相关软件相关文章发表评论 来源:本站整理时间:2026/11/2 12:11:19字体大小:A-A+

作者:xiix点击:0次评论:0次标签: MySQL慢查询 EXPLAIN 索引优化 SQL性能 数据库调优

  • 类型:数据库类大小:135.7M语言:中文 评分:10.0
  • 标签:
立即下载

上周三晚上十点多,客户的商城后台突然转圈,工单里只有一句「查询订单卡住」。我登录数据库服务器一看,CPU 跑满 92%,进程列表里挂着几条 SHOW PROCESSLIST 全是同一条 SELECT 在执行。一条不带参数的订单列表 SQL,跑了 40 秒还没出来。这篇文章不讲大道理,就讲我那天晚上是怎么用 EXPLAIN 把这条 SQL 揪出来的,以及后来发现索引其实一直都在,只是数据库根本没去用它。

如果你现在手头只有一台数据库服务器在跑生产业务,可以先看看这两家的机器规格再动手:RackNerd(全球VPS,可按需升配)、狗云DOGYUN(高性价比云服务器,多机房可选)。机器性能只是下限,真正决定一条慢查询快不快的,是下面这套排查顺序。

数据库服务器机房机柜内部特写,象征慢查询背后的物理资源

一、第一步不是加索引,是先确认它慢在哪

很多人一看到慢,第一反应是「给字段加个索引」。方向没错,但顺序反了。我那天先做的是三件事:

第一,用 SHOW FULL PROCESSLIST 或者 performance_schema 里的语句,把当前正在跑的慢 SQL 抓出来。要注意,processlist 是瞬时快照,语句跑完就没了,抓不到就换成慢查询日志。这个坑我踩过当时一条 SQL 跑 3 秒就结束了,我盯了十分钟 processlist 什么都没捞到,最后是把慢查询日志阈值临时调低才抓住。

第二,把慢查询日志打开。很多云主机的 MySQL 默认 long_query_time 是 10 秒,意味着一条 SQL 要慢到 10 秒才被记录,这对你排查 3 秒级的语句基本没用。线上排查期间我会临时把它调到 0.5 秒左右,抓完再调回去,不然日志文件会疯长。

第三,先看看是不是并发问题。同一条 SQL 单独跑只要 0.3 秒,并发一上来就 40 秒,这种是锁等待或者连接数打满,不是执行计划的问题。当时我差点走错方向,是发现 SHOW PROCESSLIST 里大量 State 是 Waiting for table metadata lock,才把注意力转到别的地方。慢的原因有很多种,执行计划只是其中一种。

二、EXPLAIN 到底该看哪几列

EXPLAIN 的输出看着一大片,其实真正要盯的就四个字段:type、key、rows、Extra。

type 是访问类型,从好到差大致是 const、eq_ref、ref、range、index、ALL。全表扫描是 ALL,扫了几十万行那种就是它。如果 type 已经到了 ALL,问题基本不用往下看了,先想办法加索引或者改写 SQL。

key 是实际用到的索引名字。这里是 NULL,说明优化器压根没用索引。注意区分两种 NULL:一种是因为压根没索引可用,另一种是有索引但优化器判断走全表更划算,主动放弃了。区分的办法是加 FORCE INDEX 强制走一次看结果,如果强制之后反而快了,那问题出在优化器的成本估算上,通常是统计信息过期了,跑一遍 ANALYZE TABLE 就能纠正。

rows 是预估要扫描的行数。这个数字是估算值不是精确值,但它能帮你一眼看出量级。索引明明存在,rows 却是几十万,那就是索引选择性太差比如你给性别、状态这种只有几个值的字段建了索引,区分度不够,优化器自然放弃。

Extra 里最值得警惕的两个值是 Using filesort 和 Using temporary。Using filesort 意思是排序没法走索引,得额外排一次;Using temporary 意味着用到了临时表,通常是 GROUP BY 或者 DISTINCT 的锅。真的出现 Using filesort 不一定是坏事,小结果集排序很便宜,怕的是它和 rows 很大同时出现。

MySQL执行计划分析界面,展示type、key、rows等关键字段

三、我那条 SQL 到底为什么慢

回到那天晚上的案例。EXPLAIN 打出来是这样的:

type 是 ALL,key 是 NULL,rows 预估 48 万,Extra 里挂着 Using where 和 Using filesort。表上是有索引的,而且不止一个order 表上有 idx_create_time 和 idx_user_id 都在,EXPLAIN 一个都没用上。

原因查出来是三个叠加在一起:

一是这条 SQL 的 WHERE 条件里带了个函数对时间字段用了 DATE(create_time),索引直接失效,这一点我之前在别的文章里提过,属于老坑;二是排序字段是 create_time DESC,MySQL 8 之前对 DESC 排序用不上升序索引,要专门建一个降序索引才行;三是数据量涨到快 50 万之后,优化器的成本估算出了偏差,判断全表扫描加 filesort 比走索引回表还划算,于是选择了扫全表。

最后的解法是三管齐下:把 DATE(create_time) 改成范围条件、单独建了一个带 DESC 的联合索引、再跑 ANALYZE TABLE 更新统计信息。改完之后同一条 SQL 从 40 秒降到 120 毫秒,EXPLAIN 里 type 变成了 range,key 也不再是 NULL。

数据库表结构与索引的抽象可视化,展示字段与索引的对应关系

四、几个容易白忙活的细节

说说那些看起来是索引问题、其实是别的问题的情况。

隐式类型转换最隐蔽。表里 user_id 是 varchar 类型,代码里却用数字 12345 去查,MySQL 会把每一行的 varchar 转成数字再比较,索引直接作废。用 EXPLAIN 看 type 变成 ALL 就能发现。解决办法要么改字段类型,要么查询时用 CAST 显式转换,两种都比让全表转一遍强。

字符集不一致也一样。join 的两个字段一个是 utf8mb4 一个是 utf8mb3,MySQL 会认为需要转换,同样放弃索引。COLLATE 改一致就解决了。

最左前缀原则不用背得太死,但复合索引 (a, b, c) 只能用 a、a+b、a+b+c 这三种组合去查,单独查 b 是走不上的。我见过有人建了 (a,b,c) 三个字段的索引,然后一直用 b 去查然后一直加索引,其实问题在查询条件。

还有一种是 ORDER BY 和 WHERE 用的是不同列。WHERE 走 idx_a,排序走不了索引,就出现了 Using filesort。这不一定得加索引,改成让排序字段也进索引的复合索引里通常更划算。

工程师在笔记本电脑前分析SQL执行计划的工作场景

五、日常怎么让它别再慢回去

单次排查只能救火,得把防线建起来。我的做法有三条。

第一条是把慢查询日志常开,long_query_time 设到 1 秒,定时把 Top SQL 捞出来看。就算没有故障,也能提前发现趋势一条 SQL 从 20 毫秒涨到 800 毫秒,通常是数据量到临界点了,早发现早处理。

第二条是上线前用真实数据量的数据副本跑一次 EXPLAIN。别在测试库上验,测试库只有几百行,全表扫描照样很快,一到生产几十万行就露馅。造点假数据把量撑起来,这个习惯能省下大量半夜救火的时间。

第三条是给核心查询建立基线。可以用 pt-query-digest 这类工具做慢日志分析,把 Top SQL 固化下来,每次发版前后对比执行计划有没有变。索引调整最怕的就是改 A 好了、改 B 坏了,没有基线根本看不出来。

最后再提一句,别忘了给连接数留余量。max_connections 设得太低,连接排队本身也会让「慢查询」看起来更慢,这跟执行计划没关系。我那次最后一个发现也是这个:应用侧连接池配置有问题,大量连接在等待释放,跟索引一点关系没有。

深夜机房运维值班场景,象征数据库性能问题的日常守护

回头看,那条跑了 40 秒的 SQL 本质上不复杂,就是几个很常见的小问题叠在一起。慢查询排查真正考验的不是会不会写 EXPLAIN,而是你知不知道该按什么顺序去查:先确认慢在哪一段,再看执行计划,最后才动手改。顺序对了,大部分问题十分钟内就能定位到具体的那几个字符上。

    相关评论

    阅读本文后您有什么感想? 已有人给出评价!

    • 8 喜欢喜欢
    • 3 顶顶
    • 1 难过难过
    • 5 囧囧
    • 3 围观围观
    • 2 无聊无聊

    发表评论

    昵称:
    表情: 高兴 可 汗 我不要 害羞 好 下下下 送花 屎 亲亲