首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >MySQL 慢查询到底怎么排查?从 explain 到索引优化的完整流程

MySQL 慢查询到底怎么排查?从 explain 到索引优化的完整流程

原创
作者头像
用魔法才能打败魔法
发布2025-11-20 16:47:59
发布2025-11-20 16:47:59
120
举报

前言

写 MySQL 的这些年,我越来越觉得:慢查询不是某一个人写坏了 SQL,而是大家都以为没问题。一个 where 条件拼错位置,一个 like 写成 %xxx,一个字段没建索引,这些东西在 QPS 低的时候一点感觉都没有,但线上一旦达到几百 QPS,RT 就能轻松飙到 200ms 以上。尤其是一次线上事故让我彻底长记性:一个看似无害的模糊查询,晚上 2 点把整个订单库打成了 90 CPU。那天值班的是我,凌晨三点我坐在工位上盯着 explain,看着扫描行数从几十万跳到几百万,那感觉真的忘不了。

平时大家总说“看下慢 SQL 啊”。但到底怎么查?怎么分析?应该怎么改?很多新人可能只知道 explain,有时候看着 explain 的结果都不知道哪里不对。这里我就把自己这些年踩坑得来的经验,连同一些真实案例组装成一次“从慢查询日志看到最终优化”的完整流程,希望对大家有所帮助。

到底什么算慢查询?别再用感觉判断了

有些同事喜欢凭感觉,说“感觉这个 SQL 有点慢”。线上可不是这么判断,慢查询是有明确指标的,MySQL 默认认为执行超过 10 秒的 SQL 叫慢查询。生产一般都会调到 1 秒以下,甚至 300ms。我们有个项目高峰 QPS 在 600 左右,慢查询阈值是 200ms,超过这个值必定要查。

这里顺便说一句:不要把阈值设太低,否则一天能给你刷上几千条慢日志,运维直接骂你。

慢查询日志怎么开?不开就等着线上揣摩人生

很多团队线上环境居然没开慢查询日志,这真的会害死排查。打开方式很简单,只要在 my.cnf 加两行就够了:

代码语言:ini
复制
slow_query_log=1
long_query_time=0.5

也可以直接 SQL 动态开启:

代码语言:sql
复制
set global slow_query_log=1;
set global long_query_time=0.5;

记住:long_query_time 不会精确到小数,只能写成 0.5,但 MySQL 实际会记录超过 0.5 秒的。有慢日志之后,你能看到每条 SQL 的执行时间、扫描行数、锁等待时间,这些比你凭感觉靠谱太多。

排查慢 SQL,我一般这样走:先日志再 explain 最后才去改

我遇到过很多不会排查的人,一上来就把 SQL 拿去 explain,但根本不知道这个 SQL 在线上执行的是怎样的参数组合、发生了几次、占了多大比例。正确做法应该是这样的:

  1. 看慢日志,确认 SQL 样子
  2. 拿真实参数还原 SQL
  3. explain 看执行计划
  4. 看索引结构、字段类型
  5. 改 SQL 或加索引
  6. 再 explain 验证
  7. 测试库跑压力验证

为什么强调真实参数?因为很多 SQL 在不同参数下会走不同索引。我踩过最恶心的一个坑:where user_id in (...),用户少的时候走索引,in 列表大到几十个的时候直接把我拖回全表扫,导致扫描行数从 50 行变成 20 万,RT 从 5ms 变成 150ms。

explain 到底怎么看?哪些字段才是重点?

很多 explain 字段其实根本不用全部背,我工作里最常看的无非这几个:type、key、rows、extra。

type 指查询方式,越往右越垃圾

你只要记住一点:type 值的质量排序 roughly 是这样排的:

代码语言:txt
复制
const > eq_ref > ref > range > index > ALL

看见 ALL 基本就是全表扫。某次事故就是因为 type=ALL,直接扫 40 万行。

extra 里面的“Using filesort”“Using temporary”都不是什么好词

这个地方坑特别多,其中我最讨厌看到的就是:

代码语言:txt
复制
Using temporary
Using filesort

文件排序意味着你 order by 的字段没有索引或者索引没命中,线上一旦遇到并发,这种 SQL 轻轻松松把磁盘 IOPS 干冒烟。

这些 SQL 写法最容易让索引失效

索引失效一般都不是“天灾”,都是人祸,而且大家常犯的几种场景非常固定,列几个我经常遇到的:

like '%xxx' 带前缀百分号的,基本直接废掉索引

这种写法千万别用,线上 90 CPU 那次,我们的订单搜索接口就是因为 like '%keyword%',type 直接变 ALL。

正确写法一般是:

  • 接入 ES
  • 或者 like 'xxx%' 限制左匹配

想全部匹配就必须用索引不适合的方案,千万别强行用 MySQL。

字段计算导致索引无效

代码语言:sql
复制
where date(create_time) = '2024-01-01'

这类写法直接让 MySQL 放弃索引,因为你对 create_time 做了函数计算。

正确写法:

代码语言:sql
复制
where create_time >= '2024-01-01' and create_time < '2024-01-02'

左边不匹配,组合索引直接报废

假设你建了组合索引 (a, b, c),那么必须遵守最左前缀,比如 where b=xx 根本走不了。这是我见过最多人搞错的,包括经验很久的人都会偶尔忘。

范围查询为什么也会让索引用不满?

where a > 3 and b = 1。很多人以为 b 会继续命中索引,结果 explain type 却是 range,只用到了 (a) 部分。原因也很简单:范围查询会让后面的字段没办法做有序判断。

这就是为什么组合索引的顺序非常重要。如果你的查询是 order_id between xxx and yyy and user_id = xxx,那索引最好是 (order_id, user_id)。

我之前因为这个顺序写反了,线上扫描行数从几百跳到五万,整整排查了一个下午。

怎么选字段做索引?不是越多越好,写操作会哭

实际工作中,我选索引的几个经验:字段区分度高、高频过滤、经常出现在 where、排序、join 上的字段优先建索引;布尔字段、性别这种几乎无区分度的字段一般别建。还有一点很多人忽略:索引会大幅增加写入成本。我参与的一个项目订单表有 11 个索引,每次写入消耗都很高,后来删掉了两个没人用的索引,写入 RT 从 20ms 降到 7ms。

索引不是越多越好,这是真理。

覆盖索引不回表,为什么这么香?

解释之前先说一段实际踩坑。我曾经查一个用户表,SQL 是:

代码语言:sql
复制
select id, name from user where phone = 'xxxx'

phone 字段有索引,但是因为 name 不在二级索引里,所以 MySQL 会回表再查一次,rows 明明只有 1 行,但 IO 开销不小。后来我加了组合索引 (phone, name),直接覆盖索引,不需要回表,RT 从 5ms 降到 1ms。回表的问题就是:你只查一个值,却需要查两次存储结构,热点数据会慢得特别明显。

表结构设计和索引优化一定要一起看,不要分开搞

有些团队喜欢一个人写表结构,一个人写 SQL,一个人调索引,结果全乱套。有一次我们数据库里有个 varchar(200) 的手机号字段,索引就 200 字节宽,导致 BTREE 巨大。后来改成 varchar(20),索引大小直接缩 10 倍。表结构设计永远不是无关紧要的。字段长度越短,索引越小,缓存越容易命中。

真实的慢 SQL 优化案例:从 800ms 砍到 8ms

讲一个我亲手优化的例子,印象特别深。线上报警:某用户查询接口 RT 频繁超过 800ms。慢日志抓到 SQL:

代码语言:sql
复制
select * from order where user_id = 10001 and status = 1 order by create_time desc limit 20;

看似毫无问题对吧?但 explain 一看傻眼了:type=ALL rows=40万 extra=Using filesort

原因很简单:status 在 where 中排在 user_id 前面,导致走不上 (user_id, status, create_time) 的组合索引。最终方案是:

  1. 调整 SQL 字段顺序
  2. 补上合适顺序的组合索引

改完后:

type=range rows=50 extra=Using index,RT 直接从 800ms 掉到 8ms,并发高峰时 CPU 占用下降了 30。

加索引是把读优化到极致,但写操作会越来越慢。如果你的业务是写多读少,那别一股脑给字段加索引。我遇到过一个团队,一个订单表建了 15 个索引,写入每秒只有区区 100 左右,后来删成 7 个索引之后,写入性能直接翻倍。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
    • 到底什么算慢查询?别再用感觉判断了
    • 慢查询日志怎么开?不开就等着线上揣摩人生
    • 排查慢 SQL,我一般这样走:先日志再 explain 最后才去改
    • explain 到底怎么看?哪些字段才是重点?
      • type 指查询方式,越往右越垃圾
      • extra 里面的“Using filesort”“Using temporary”都不是什么好词
    • 这些 SQL 写法最容易让索引失效
      • like '%xxx' 带前缀百分号的,基本直接废掉索引
      • 字段计算导致索引无效
      • 左边不匹配,组合索引直接报废
    • 范围查询为什么也会让索引用不满?
    • 怎么选字段做索引?不是越多越好,写操作会哭
    • 覆盖索引不回表,为什么这么香?
    • 表结构设计和索引优化一定要一起看,不要分开搞
    • 真实的慢 SQL 优化案例:从 800ms 砍到 8ms
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档