首页 / 互联网开发 / 正文

MySQL 慢查询排查与索引优化:EXPLAIN 读到烂熟

线上 SQL 慢到报警,打开慢查询日志一看,几乎都是同一个原因:查询没有走索引,在扫全表。索引优化不是背概念,而是要能回答三个问题:索引底层怎么组织、为什么有的查询用不上索引、EXPLAIN 到底怎么看。这篇用真实场景串起来。

一、先理解 B+ 树:为什么索引能加速

InnoDB 的索引底层是 B+ 树,几个关键特性:

  • 矮胖:每个节点能存大量子节点(高扇出),三层树就能覆盖千万级数据,查询时磁盘 IO 次数极少(树高就是 IO 次数);
  • 叶子节点存数据并双向链表相连:所以范围查询(BETWEEN、大于小于)只需要顺着叶子链表走,非常高效;
  • 数据有序存储:等值、范围、排序都能利用这个有序性。
理解“有序”是理解索引的钥匙:为什么 WHERE name LIKE '张%' 能用索引而 LIKE '%张' 不能?因为前者匹配的是前缀,B+ 树里数据按前缀有序;后者要匹配后缀,顺序帮不上忙。

二、聚簇索引与二级索引:回表是怎么回事

类型叶子节点内容说明
聚簇索引(主键)完整行数据InnoDB 每张表必须有一个,按主键组织
二级索引(普通索引)索引列值 + 主键值通过二级索引查到主键,再回聚簇索引取整行

所以 SELECT * 走二级索引后通常要回表(多一次主键查找);如果查询的列恰好都在索引里(覆盖索引),就不用回表,EXPLAIN 里表现为 Using index——这是让查询再快一档的常用手段。

三、联合索引:最左前缀原则

联合索引 (user_id, status, create_time) 的查找顺序是先按 user_id、再按 status、再按 create_time,相当于建了三棵“前缀有序”的树,能匹配的条件组合是:

-- ✅ 能用(最左前缀)
WHERE user_id = 1
WHERE user_id = 1 AND status = 2
WHERE user_id = 1 AND status = 2 AND create_time > '2024-01-01'
-- ❌ 用不上(跳过了最左列)
WHERE status = 2 AND create_time > '2024-01-01'
联合索引里把等值列放前面、范围列放后面是基本纪律:范围条件(>、<、BETWEEN)之后的列无法继续用索引过滤,写 EXPLAIN 前先按这个顺序自查。

四、索引为什么失效:最常见的六个坑

  1. 对索引列做函数或运算WHERE YEAR(create_time) = 2024 会让索引失效,改为 create_time BETWEEN '2024-01-01' AND '2024-12-31'
  2. 隐式类型转换:列是字符串却用数字比较(或反之),MySQL 会转类型导致无法走索引;
  3. 前导模糊查询LIKE '%keyword'
  4. OR 连接未全部有索引a = 1 OR b = 2,若 b 无索引则整体难走索引,可拆 UNION;
  5. 索引区分度太低:列只有两三个取值(如 status),优化器认为扫全表更快,会主动放弃索引;
  6. 排序/分组字段组合不匹配索引顺序:出现 Using filesort / Using temporary。

五、EXPLAIN 实战:每列看什么

EXPLAIN SELECT * FROM t_order
WHERE user_id = 100 AND status = 1 ORDER BY create_time DESC LIMIT 20;
-- 重点看这几列:
-- type      : 从好到坏 system > const > eq_ref > ref > range > index > ALL
-- key       : 实际用到的索引,NULL 说明没走索引
-- rows      : 预估扫描行数,数量级越接近实际结果越好
-- Extra     : Using index(覆盖索引)、Using filesort(需优化排序)、Using where
排查套路:先把慢 SQL 丢进 EXPLAIN → type 是 ALL 或 key 是 NULL → 查是不是命中上面的“六坑” → 改正后重看 EXPLAIN,rows 明显下降才算完。

六、建索引的正确姿势

  • 索引是“加给查询的”:拿业务里真实慢 SQL 来加,而不是拍脑袋给每列都建;
  • 控制数量:索引占空间且拖慢写入,一张表一般建议不超过五六个,经常一起查的列合并成联合索引;
  • 短列优先:优先用长度短、区分度高的列;
  • 上线前检查:用 EXPLAIN 验证后发布,大表加索引注意锁表窗口,尽量低峰期执行。

七、慢查询日志:先定位再优化

-- 开启慢查询日志(生产建议同时开启,阈值设 1 秒)
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 1;
-- 事后分析
mysqldumpslow -s t /var/log/mysql/mysql-slow.log   # 按时间排序看最慢的
# 或 pt-query-digest 汇总相同指纹的 SQL,按总耗时排

慢查询日志的价值是让优化有靶子:别等用户投诉,让日志告诉我们哪几条 SQL 最该先优化,改一条、EXPLAIN 一次、看曲线下降,重复这个过程,数据库就稳了。