在这里插入图片描述

🍃 予枫个人主页

📚 个人专栏: 《Java 从入门到起飞》《读研码农的干货日常

💻 Debug 这个世界,Return 更好的自己!

引言

高并发、海量数据场景下,一句慢SQL就能拖垮整个系统,这是每个后端开发者和架构师都避不开的坑。很多人遇到性能瓶颈时,要么盲目加索引,要么乱改SQL,结果越调越糟。其实核心秘诀就两个:读懂执行计划、找准慢查询根源。今天就手把手教你吃透EXPLAIN,结合慢查询日志实战优化,从“调优小白”进阶“架构师视角”,搞定SQL性能问题!

一、为什么EXPLAIN是SQL调优的“万能钥匙”?

在进行SQL调优前,我们首先要明确:SQL到底是怎么执行的?有没有走索引?有没有做全表扫描?有没有出现低效的排序操作?而EXPLAIN正是MySQL提供的“执行计划分析工具”,只需在SQL语句前加上EXPLAIN,就能直观看到SQL的执行细节,为调优提供精准方向。

💡 重点提示:EXPLAIN不会真正执行SQL语句(除非是SELECT类型,会读取表结构信息),所以不用担心影响线上数据,可放心使用!

举个简单例子,执行 EXPLAIN SELECT * FROM user WHERE age > 20 AND name LIKE '%zhang%'; 后,会输出包含id、select_type、table、type、possible_keys、key、key_len、ref、rows、Extra等字段的结果,其中type、rows、key、extra这四个字段是核心中的核心,也是我们调优的重点分析对象。

建议大家先点赞收藏本文,后续调优时直接对照查阅,效率翻倍!👍

二、手把手吃透EXPLAIN四大核心字段

2.1 type:查询类型(效率从低到高排序)

type字段代表MySQL在表中找到所需行的方式,也叫“访问类型”,其值直接决定了SQL的执行效率,常见取值及说明如下(重点记红框内高效类型):

类型 说明 效率等级
ALL 全表扫描,遍历整个表找到匹配行,效率最低
index 全索引扫描,遍历整个索引树,比ALL略好(索引文件通常比数据文件小) 较差
range 索引范围扫描,只扫描索引的一部分(如between、in、>、<等条件) 中等
ref 非唯一索引扫描,返回匹配某个单独值的所有行(如普通索引匹配) 较好
eq_ref 唯一索引扫描,对于每个索引键,表中只有一条记录匹配(如主键/唯一键关联)
const/system 常量查询,表中只有一条记录匹配,效率最高(如根据主键查询单条数据) 最好

实战案例:

-- 1. 全表扫描(type=ALL)
EXPLAIN SELECT * FROM user WHERE age = 25; 
-- 原因:age字段未建索引,MySQL只能遍历整个user表

-- 2. 唯一索引扫描(type=eq_ref)
EXPLAIN SELECT u.name, o.order_no FROM user u JOIN order o ON u.id = o.user_id;
-- 原因:u.id是主键(唯一索引),o.user_id关联主键,每次匹配只有一条记录

2.2 rows:预估扫描行数

rows字段表示MySQL预估要扫描的行数,行数越少,执行效率越高。需要注意的是,这是一个预估值,不是精确值,但能直观反映SQL的低效程度。

比如同样是查询用户信息,A SQL的rows=10000,B SQL的rows=10,显然B SQL的效率更高。调优的核心目标之一,就是通过优化索引、调整SQL,让rows值尽可能小。

2.3 key:实际使用的索引

key字段表示MySQL实际执行SQL时使用的索引,如果该字段为NULL,说明SQL没有使用任何索引,大概率是低效查询(除非表数据量极小)。

重点注意:

  • possible_keys:表示可能会使用的索引(候选索引),但不一定实际使用;
  • key:表示实际使用的索引,只有当key字段有值时,才说明索引真正生效。

如果possible_keys有值但key为NULL,可能是因为索引选择性太差(如性别字段,只有男/女两个值),MySQL认为全表扫描比走索引更高效。

2.4 Extra:额外执行信息(调优关键信号)

Extra字段包含了SQL执行的额外信息,很多时候能直接定位到调优痛点,常见关键值及处理方案如下:

2.4.1 Using filesort(文件排序,严重低效)

含义:MySQL无法利用索引完成排序操作,只能在内存或磁盘中进行文件排序,效率极低,尤其是数据量大时。

优化方案:设计“覆盖索引”(将排序字段和查询字段都包含在索引中),让MySQL能通过索引直接排序,避免文件排序。

2.4.2 Using temporary(临时表,严重低效)

含义:MySQL需要创建临时表来存储中间结果,通常出现在GROUP BY、DISTINCT等操作中,效率低下。

优化方案:优化索引,让GROUP BY/DISTINCT操作能利用索引完成,避免创建临时表。

2.4.3 Using index(覆盖索引,高效)

含义:SQL只需要通过索引就能获取所需数据,不需要回表查询数据行(即“索引覆盖”),效率极高,是调优的理想状态。

2.4.4 Using where(过滤条件)

含义:MySQL先通过索引找到匹配的行,再根据WHERE条件过滤,属于正常情况,但如果结合type=ALL(全表扫描),则需要优化。

三、慢查询日志分析:精准定位低效SQL

光会看EXPLAIN还不够,我们还需要先找到那些“拖慢系统”的慢查询SQL,这就需要用到MySQL的慢查询日志(Slow Query Log)。

3.1 开启慢查询日志

默认情况下,MySQL的慢查询日志是关闭的,我们可以通过以下命令开启(线上环境建议开启,日志存储路径需合理配置):

-- 临时开启(重启MySQL后失效)
SET GLOBAL slow_query_log = ON; -- 开启慢查询日志
SET GLOBAL long_query_time = 1; -- 设定慢查询阈值(超过1秒的SQL记录)
SET GLOBAL slow_query_log_file = '/var/lib/mysql/slow.log'; -- 日志存储路径

-- 永久开启(修改my.cnf配置文件,重启MySQL生效)
[mysqld]
slow_query_log = ON
long_query_time = 1
slow_query_log_file = /var/lib/mysql/slow.log

3.2 解读慢查询日志

慢查询日志会记录下所有执行时间超过long_query_time的SQL,每条记录包含执行时间、用户、主机、SQL语句等信息,示例如下:

# Time: 2026-02-03T10:00:00.000000Z
# User@Host: root[root] @ localhost []  Id:  1234
# Query_time: 2.500000  Lock_time: 0.000000 Rows_sent: 10  Rows_examined: 10000
SET timestamp=1770000000;
SELECT * FROM order WHERE create_time > '2026-01-01' ORDER BY total_amount DESC;
  • Query_time:执行时间(2.5秒,超过阈值1秒,被记录);
  • Rows_examined:扫描行数(10000行,效率低);
  • Rows_sent:返回行数(10行,说明大量扫描的行未被返回)。

通过慢查询日志,我们能快速定位到哪些SQL是“性能杀手”,然后针对性地用EXPLAIN分析优化。

四、实战优化:搞定Using filesort案例

结合前面的知识,我们通过一个真实案例,手把手教大家如何优化包含Using filesort的慢查询。

4.1 问题场景

有一个订单表(order),包含字段:id(主键)、user_id(用户ID)、create_time(创建时间)、total_amount(订单金额)、status(订单状态),数据量10万条。执行以下SQL时,查询时间超过2秒,且EXPLAIN显示存在Using filesort:

-- 慢查询SQL
SELECT id, total_amount, status FROM order 
WHERE user_id = 100 AND create_time > '2026-01-01' 
ORDER BY total_amount DESC;

4.2 分析过程(EXPLAIN)

执行 EXPLAIN 上述SQL,得到核心结果:

  • type:ref(user_id字段已建普通索引);
  • key:idx_user_id(使用了user_id的索引);
  • Extra:Using filesort(存在文件排序,低效)。

原因:虽然user_id字段有索引,但排序字段是total_amount,MySQL无法利用idx_user_id索引完成排序,只能在查询后进行文件排序,导致执行效率低。

4.3 优化方案:创建覆盖索引

设计一个“联合索引”,包含查询条件字段(user_id、create_time)和排序字段(total_amount),同时包含查询结果字段(id、status),让MySQL能通过索引直接完成查询和排序,避免文件排序。

创建联合索引:

CREATE INDEX idx_user_create_total ON order (user_id, create_time, total_amount);
  • 索引顺序说明:先放等值查询字段(user_id),再放范围查询字段(create_time),最后放排序字段(total_amount);
  • 覆盖性:该索引包含了SQL中的查询条件(user_id、create_time)、排序字段(total_amount)和返回字段(id是主键,InnoDB中主键会自动包含在二级索引中,status需包含在索引中?不,InnoDB二级索引叶子节点存储主键,查询status需回表?这里调整:将返回字段也包含进索引,形成覆盖索引)。

修正联合索引(包含返回字段status):

CREATE INDEX idx_user_create_total_status ON order (user_id, create_time, total_amount, status);

4.4 优化后验证

再次执行EXPLAIN分析优化后的SQL,核心结果:

  • type:range(user_id等值匹配,create_time范围查询,走索引);
  • key:idx_user_create_total_status(使用了新创建的联合索引);
  • rows:10(预估扫描行数大幅减少);
  • Extra:Using index(覆盖索引,无文件排序)。

查询时间从2.5秒缩短至0.01秒,优化效果显著!🎉

五、总结

本文从架构师视角出发,围绕SQL调优核心,讲解了EXPLAIN四大核心字段(type、rows、key、extra)的解读方法,结合慢查询日志的开启与分析,通过真实案例实战了Using filesort的优化技巧。核心要点如下:

  1. EXPLAIN是SQL调优的核心工具,重点关注type(优先eq_ref、ref、range)、rows(越少越好)、key(非NULL)、extra(避免Using filesort/Using temporary);
  2. 慢查询日志能精准定位低效SQL,线上环境建议开启,阈值根据业务场景调整;
  3. 优化核心思路:通过合理设计索引(如联合索引、覆盖索引),让SQL尽可能走索引,减少扫描行数和额外操作。
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐