慢查询优化 10 招:从 EXPLAIN 到索引重建

前言

慢查询优化面试题:

线上接口突然变慢,你定位到一条 SQL 慢查询,怎么优化?

90% 的人回答:加索引

加索引确实是最常见手段,但追加了 3 个问题就卡壳:

  1. EXPLAIN 的 type 字段哪个值代表全表扫描? ------ 全 12 个字段你能看懂几个
  2. 索引加了还是慢,怎么办? ------ 不只是索引问题
  3. SQL 改了一行执行计划全变了,为什么? ------ 优化器做了什么

这篇文章从 EXPLAIN 到索引重建,把慢查询优化 10 招讲透。

一、EXPLAIN 12 个字段

慢查询第一步永远是 EXPLAIN。看懂这 12 个字段,你才算懂优化。

字段 含义 重点关注
id 查询序号 id 越大越先执行;同 id 从上往下
select_type 查询类型 SIMPLE/PRIMARY/SUBQUERY
table 表名 哪张表慢
type 访问类型 最重要,下面详解
possible_keys 可能用到的索引 为空 = 没有可用索引
key 实际用到的索引 NULL = 没走索引
key_len 索引使用长度 判断联合索引用了几个列
ref 索引比较的列 const/列名
rows 预估扫描行数 ⭐ 越小越好
filtered 过滤后剩余比例 越大越好(100% 最好)
Extra 额外信息 Using filesort/temporary 要警惕
partitions 分区表命中情况 一般忽略

type 字段速查表(核心)

graph LR A[&#34;system<br/>表只有一行&#34;] --> B[&#34;const<br/>主键/唯一索引等值&#34;] B --> C[&#34;eq_ref<br/>JOIN 唯一索引等值&#34;] C --> D[&#34;ref<br/>非唯一索引等值&#34;] D --> E[&#34;range<br/>范围查询&#34;] E --> F[&#34;index<br/>扫整个索引树&#34;] F --> G[&#34;ALL<br/>全表扫描❌&#34;] style A fill:#9f9 style G fill:#f99 style F fill:#fc6

性能从好到差system > const > eq_ref > ref > range > index > ALL

  • 至少要到 ref / range出现 ALL 必须优化
  • index 比全表扫描好一点(扫索引树,但还是要全扫,常因覆盖索引)。
ini 复制代码
EXPLAIN SELECT * FROM user WHERE id = 1;
-- type=const ✅ 主键等值

EXPLAIN SELECT * FROM user WHERE status = 1;
-- type=ALL ❌ status 无索引,全表扫描

key_len 判断联合索引用了几列

css 复制代码
联合索引 (a, b, c),a 是 BIGINT(8字节)
key_len = 8  → 只用了 a
key_len = 16 → 用了 a, b
key_len = 24 → 用了 a, b, c

key_len 是判断联合索引用到第几列 的关键,常配合 refrows 一起看。

Extra 常见值

Extra 值 含义 是否要优化
Using index 覆盖索引,不回表 ✅ 最理想
Using where 服务层过滤 一般正常
Using filesort 无法用索引排序,额外排序 ⚠️ 大数据量要优化
Using temporary 用了临时表 ⚠️ 高成本,必须优化
Using join buffer JOIN 无索引用块嵌套 ⚠️ 加 JOIN 索引

二、索引失效 8 种场景

# 场景 反例 原因
1 函数操作 WHERE YEAR(create_time)=2026 破坏索引有序性
2 隐式类型转换 phone=13800138000(phone 是 varchar) 类型不匹配转全扫
3 左模糊 name LIKE '%张' B+ 树无法定位
4 OR 连接 a=1 OR b=2(b 无索引) 一半走全扫
5 != / NOT IN status != 1 优化器选全扫
6 NULL 判断 col IS NULL(索引未允许 NULL) 视建索引而定
7 计算操作 id+1 = 10 破坏了索引列
8 联合索引非最左 索引(a,b,c),WHERE b=1 违反最左前缀
graph TB Q[&#34;SQL 走索引吗?&#34;] --> F{&#34;是否对索引列<br/>做函数/计算?&#34;} F -->|是| FAIL1[&#34;❌ 失效&#34;] F -->|否| C{&#34;是否左模糊<br/>或类型不匹配?&#34;} C -->|是| FAIL2[&#34;❌ 失效&#34;] C -->|否| D{&#34;联合索引<br/>满足最左前缀?&#34;} D -->|否| FAIL3[&#34;❌ 失效&#34;] D -->|是| OK[&#34;✅ 走索引&#34;] style FAIL1 fill:#f99 style FAIL2 fill:#f99 style FAIL3 fill:#f99 style OK fill:#9f9

三、SQL 重写优化 4 招

招 1:避免 SELECT *

sql 复制代码
-- ❌ 全列查询,无法覆盖索引
SELECT * FROM user WHERE name = '张三';

-- ✅ 只查需要的列,可能命中覆盖索引
SELECT id, name, age FROM user WHERE name = '张三';

招 2:避免对索引列做函数/计算

sql 复制代码
-- ❌ 函数包裹索引列
SELECT * FROM orders WHERE YEAR(create_time) = 2026;

-- ✅ 改成范围查询
SELECT * FROM orders
WHERE create_time >= '2026-01-01' AND create_time < '2027-01-01';

招 3:避免左模糊,用右模糊

sql 复制代码
-- ❌ 左模糊,索引失效
WHERE name LIKE '%张';

-- ⚠️ 全模糊也失效,必要时用全文索引
WHERE name LIKE '%张三%';

-- ✅ 右模糊可走索引
WHERE name LIKE '张%';

招 4:OR 改成 UNION ALL

sql 复制代码
-- ❌ a 有索引 b 无索引,整体全扫
SELECT * FROM user WHERE a = 1 OR b = 2;

-- ✅ 拆成两个查询
SELECT * FROM user WHERE a = 1
UNION ALL
SELECT * FROM user WHERE b = 2;

UNION ALL 比 UNION 快(不去重),生产配合 EXPLAIN 确认是否值得。

四、慢查询排查流程

线上慢查询不是靠猜,有一套标准流程。

graph TB A[&#34;开启 slow_query_log&#34;] --> B[&#34;定位慢 SQL<br/>(long_query_time 阈值)&#34;] B --> C[&#34;EXPLAIN 分析&#34;] C --> D{&#34;type=ALL<br/>或 rows 巨大?&#34;} D -->|是| E[&#34;加索引&#34;] D -->|否| F[&#34;重写 SQL<br/>避免函数/左模糊/OR&#34;] E --> G[&#34;重新 EXPLAIN 确认&#34;] F --> G G --> H{&#34;还是慢?&#34;} H -->|否| I[&#34;✅ 完工&#34;] H -->|是| J[&#34;数据量大?<br/>考虑分库分表/读写分离&#34;] style A fill:#9f9 style I fill:#9f9 style J fill:#fc6
sql 复制代码
-- 第 1 步:开启慢查询日志
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 1;  -- 超过 1s 记录

-- 第 2 步:查慢日志
SELECT * FROM mysql.slow_log ORDER BY start_time DESC LIMIT 10;

-- 第 3 步:EXPLAIN
EXPLAIN SELECT * FROM user WHERE name = '张三';

-- 第 4 步:加索引
ALTER TABLE user ADD INDEX idx_name(name);

五、高频面试题

Q1: EXPLAIN 哪个字段最重要?

typerowsExtra 三个最关键。type 看访问类型(ALL 全表扫描必须优化),rows 看预估扫描量,Extra 中的 Using filesort / Using temporary 是性能杀手。

Q2: type 字段从好到差怎么排?

system > const > eq_ref > ref > range > index > ALL。至少要到 ref/rangeALL 必须加索引。

Q3: 加了索引还是慢怎么办?

EXPLAINkey 是否真用上索引,key_len 看联合索引用了几列。没用上说明命中了失效场景(函数、左模糊、非最左前缀)。用上了还慢,看 rows 是否太大,考虑重写 SQL、限量、分库分表。

Q4: key_len 有什么用?

判断联合索引用到了第几列。一个 BIGINT 8 字节,联合索引 (a,b,c) key_len=16 说明用了 a、b 两列。

Q5: 索引失效有哪些常见场景?

函数操作、隐式类型转换、左模糊、OR 连接无索引列、!= / NOT IN、计算操作、联合索引非最左前缀、NULL 判断(部分情况)。

Q6: Using filesort 一定有问题吗?

不一定。小数据量排序很快。但是大数据量排序用它就慢------加排序字段到索引里,让排序走索引即可避免。

六、面试速记 10 条

# 要点
1 EXPLAIN 核心看 type / rows / Extra
2 type 排序:const > ref > range > index > ALL
3 ALL = 全表扫描,必须优化
4 key_len 判断联合索引用几列
5 Using filesort / temporary 要警惕
6 索引失效 8 种:函数、左模糊、类型转换、OR、非最左
7 *避免 SELECT ,用覆盖索引
8 YEAR() 改范围查询,左模糊改右模糊
9 慢查询流程:slow_log → EXPLAIN → 加索引/重写
10 优化完用 EXPLAIN 复核,别凭感觉

七、总结

一句话总结

慢查询优化核心是看懂 EXPLAIN:type 判断访问类型(ALL/聚合索引要警惕),rows 看扫描量,Extra 找 filesort/temporary。先慢日志定位,再 EXPLAIN 分析,能加索引加索引(注意 8 种失效场景),能重写重写(避免 SELECT *、函数包裹、左模糊、OR)。优化完一定要再 EXPLAIN 复核。

相关推荐
用户40966601317511 小时前
MySQL 事务与锁机制实战:从隔离级别到死锁排查
后端
zhangxingchao1 小时前
AI应用开发九:从 Hermes Agent 学长期记忆
前端·人工智能·后端
默默敲代码的徐哥儿2 小时前
八股文整理——后端
java·开发语言·spring boot·后端·学习
自由的裙子2 小时前
ASP.NET路由模型解析
后端·asp.net
k↑2 小时前
SpringCloud + React19 集成Scalar的API文档
后端·spring·spring cloud
Mandy的名字被占用了2 小时前
Dart 与 Flutter 快速入门指南
后端·flutter·dart
货拉拉技术2 小时前
基于eBPF的主机安全探索与实践
后端
梨子同志3 小时前
Spring Security
后端
名字还没想好☜3 小时前
Spring Boot 全局异常处理:@ControllerAdvice 实战
java·spring boot·后端·spring·异常处理