MySQL SQL 从 EXPLAIN 到索引优化,搞懂 SQL 为什么慢

一、SQL 为什么会慢?

SQL 查询慢,一般可以归结为以下几个原因:

  • 没有走索引(全表扫描)
  • 索引失效
  • 回表次数过多
  • 排序(Filesort)
  • 临时表(Using Temporary)
  • JOIN 数据量过大
  • 返回数据过多
  • SQL 写法不合理

尽可能减少 MySQL 需要扫描的数据量。

优化器负责决定:

  • 是否使用索引
  • 使用哪个索引
  • JOIN 顺序
  • 是否覆盖索引
  • 是否排序

SQL 调优,本质就是帮助优化器找到更好的执行方案。


二、SQL 调优第一步:使用 EXPLAIN

SQL 调优工具 EXPLAIN:

sql 复制代码
EXPLAIN
SELECT *
FROM user
WHERE age = 20;
sql 复制代码
id: 1
select_type: SIMPLE
table: user
type: ref
possible_keys: idx_age
key: idx_age
rows: 120
Extra: Using index

重点关注几个字段

① type

表示访问方式,性能从好到坏:

复制代码
system
const
eq_ref
ref
range
index
ALL

const 主键唯一查询

复制代码
SELECT * FROM user
WHERE id = 1;

ref 普通索引查询


range 范围扫描

复制代码
WHERE age > 20

index 扫描整个索引,虽然不是全表扫描,但是仍然扫描所有索引

复制代码
SELECT name
FROM user;

ALL 全表扫描

复制代码
SELECT *
FROM user;

② key

真正使用的索引。

sql 复制代码
key = idx_age # 使用的是INDEX(age)
sql 复制代码
key = NULL # 说明没使用任何索引

③ rows

预计扫描多少行。

sql 复制代码
rows = 20 # 说明预计扫描20行数据
sql 复制代码
rows = 500000 # 扫描了几十万行,这通常就是慢 SQL。

④ Extra

这是调优中最值得关注的字段,常见值:

Using index(最好)

使用了覆盖索引,不用回表,效率最高。


Using where

表示:读取数据后再过滤,正常现象。


Using filesort(需要优化)

sql 复制代码
ORDER BY age;

没有对应索引,MySQL 需要额外排序,性能较差。


Using temporary(需要优化)

通常出现在:

复制代码
GROUP BY
DISTINCT

说明:创建了临时表,一般建议优化。


三、覆盖索引、回表

  • 主键索引的 B+Tree 的叶子节点存放的是实际数据,所有完整的用户记录都存放在主键索引的 B+Tree 的叶子节点里;
  • 二级索引的 B+Tree 的叶子节点存放的是主键值,而不是实际数据。

所以,在查询时使用了二级索引,如果查询的数据能在二级索引里查询的到,那么就不需要回表,这个过程就是覆盖索引。

如果查询的数据不在二级索引里,就会先检索二级索引,找到对应的叶子节点,获取到主键值后,然后再检索主键索引,就能查询到数据了,这个过程就是回表。

现在建立索引 (age,name),执行下面的SQL语句:

sql 复制代码
SELECT name
FROM user
WHERE age = 20;

索引中已经有 age 和 name,不用回表

Extra:

复制代码
Using index

四、索引失效的常见原因

① 对索引列使用函数

sql 复制代码
WHERE YEAR(create_time)=2025;

索引保存的是:create_time 而不是 YEAR(create_time)

sql 复制代码
WHERE create_time >= '2025-01-01'
AND create_time < '2026-01-01';

② 对索引列计算

sql 复制代码
WHERE age+1=20;

改成:

sql 复制代码
WHERE age=19;

③ 隐式类型转换

SQL:

sql 复制代码
phone VARCHAR
WHERE phone = 123456;

MySQL会将字符串 > 数字,转换导致索引失效。

正确方式:

复制代码
WHERE phone='123456';

④ LIKE '%abc'

sql 复制代码
LIKE '%mysql' 
LIKE '%abc%'

因为模糊匹配最左边是 % ,索引无法定位,只能全表扫描。

但是这样是可以的:

sql 复制代码
LIKE 'mysql%'

⑤ 最左匹配原则

最左匹配原则 也就是按照最左优先的方式进行索引的匹配,不遵循的话,联合索引会失效

比如,如果创建了一个 (a, b, c) 联合索引,如果查询条件是以下这几种,就可以匹配上联合索引:

  • where a=1;
  • where a=1 and b=2 and c=3;
  • where a=1 and b=2;

需要注意的是,因为有查询优化器,所以 a 字段在 where 子句的顺序并不重要。

如果是 where b=2 and c =3; 就不满足最左匹配原则,不会用索引。

五、常见场景优化

ORDER BY 如何优化?

sql 复制代码
SELECT *
FROM user
ORDER BY age;

对 ORDER BY 后的字段建立索引 INDEX(age),排序就可以直接利用索引,避免额外排序


LIMIT 如何优化?

sql 复制代码
SELECT *
FROM article;

返回几十万行,但实际上前端一次只显示20条

优化:使用 LIMIT 限制查询行数据量

sql 复制代码
SELECT *
FROM article
LIMIT 20;

数据库扫描更少,网络传输更少,速度明显提高。


JOIN 如何优化?

sql 复制代码
SELECT *
FROM orders o
JOIN user u
ON o.user_id=u.id;

需要保证:orders.user_id,user.id 都有索引,否则JOIN 会非常慢。


六、SQL 调优流程

实际开发中,可以按照下面的步骤排查:

复制代码
SQL 很慢
      │
      ▼
EXPLAIN
      │
      ▼
有没有走索引?
      │
      ├──没有
      │      │
      │      ▼
      │ 建索引
      │
      ▼
rows 是否很大?
      │
      ▼
是否扫描太多数据?
      │
      ▼
是否回表?
      │
      ▼
是否覆盖索引?
      │
      ▼
Extra 是否 Filesort?
      │
      ▼
Extra 是否 Temporary?
      │
      ▼
优化 SQL 写法

这个流程覆盖了大部分慢查询排查场景。


七、总结

可以记住下面这几条:

  • 先用 EXPLAIN,看执行计划。
  • 优先让 SQL 走索引,避免全表扫描。
  • 减少扫描行数(rows 越小越好)。
  • 尽量使用覆盖索引,减少回表。
  • 避免索引失效:不要对索引列使用函数、计算或隐式类型转换。
  • 遵循联合索引最左匹配原则。
  • ORDER BY、GROUP BY 尽量利用索引,避免 Filesort 和 Temporary。
  • 分页避免深分页,可使用游标分页(Keyset Pagination)。
  • JOIN 字段建立索引,小表驱动大表。
  • SQL 调优的核心目标:减少扫描数据、减少随机 IO、降低 CPU 与排序开销。

相关推荐
zhangphil1 小时前
Android OAID是什么?有什么功用?
android
辰同学ovo2 小时前
从一条 SQL 到三个原则:MySQL 心智模型
sql·mysql·adb
Android-Flutter4 小时前
android fragment 使用
android·kotlin
迷茫中的自我4 小时前
KMP全栈开发:从Android到AI Agent的技术演进与实践
android·人工智能
随遇丿而安4 小时前
第13周:页面状态保存 + 数据恢复优化
android
万事可爱^5 小时前
Claude 新发布的 Opus 5,系统提示语删了 80%,半价还能逼近 Fable 5
android·服务器·数据库·人工智能·claude
Mico185 小时前
MySQL 8.0.35 GTID主从复制常见管理操作
mysql
alexhilton5 小时前
响应式的Android身份验证架构
android·kotlin·android jetpack
网安墨雨6 小时前
MySQL数据库 SQL语句详解
自动化测试·软件测试·数据库·python·sql·mysql