一、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 与排序开销。