视频看了几百小时还迷糊?关注我,几分钟让你秒懂!


在 Spring Boot 项目中,你是不是经常遇到这样的问题:

  • 接口突然变慢,SQL 查询要好几秒?
  • 明明加了索引,但查询还是全表扫描?
  • 不知道 MySQL 到底有没有用上你的索引?

这时候,EXPLAIN 就是你最强大的“透视镜”
它能告诉你:MySQL 是怎么执行这条 SQL 的?用了什么索引?扫了多少行?

今天我们就手把手教你 看懂 EXPLAIN 的每一列含义,并结合 Java + Spring Boot 实战,让小白也能快速定位性能瓶颈!


一、什么是 EXPLAIN?

EXPLAIN 是 MySQL 提供的一个命令,用于分析 SQL 语句的执行计划(Execution Plan)

你不需要真正执行 SQL,就能知道:

  • 会不会走索引?
  • 会扫描多少行?
  • 是否需要临时表或文件排序?

基本用法

EXPLAIN SELECT * FROM user_info WHERE phone = '13800138000';

返回结果是一个表格,包含若干列(通常 10+ 列),我们重点看 7 个核心字段


二、EXPLAIN 核心字段详解(附实战案例)

我们以一张用户订单表为例:

CREATE TABLE order_info (
    id BIGINT PRIMARY KEY,
    user_id BIGINT NOT NULL,
    order_status TINYINT NOT NULL,  -- 0:待支付, 1:已支付
    create_time DATETIME NOT NULL,
    amount DECIMAL(10,2) NOT NULL
);

-- 只建了一个单列索引
CREATE INDEX idx_user ON order_info(user_id);

现在执行:

EXPLAIN 
SELECT * FROM order_info 
WHERE user_id = 1001 
  AND order_status = 1 
  AND create_time >= '2026-01-01';

返回结果如下(简化):

id select_type table type possible_keys key key_len rows Extra
1 SIMPLE order_info ref idx_user idx_user 8 50 Using where

下面逐个解释!


1️⃣ id:查询序列号

  • 表示 SELECT 子句或操作表的顺序
  • id 相同:从上到下执行
  • id 不同:id 越大越先执行(如子查询)

✅ 一般单表查询都是 id=1,不用太关注。


2️⃣ select_type:查询类型

含义
SIMPLE 简单查询(无子查询、UNION)✅ 最常见
PRIMARY 最外层查询
SUBQUERY 子查询
DERIVED FROM 中的子查询(派生表)
UNION UNION 中的第二个或之后的 SELECT

📌 重点关注是否是 SIMPLE,复杂查询可能性能差。


3️⃣ table:表名

  • 当前操作的表
  • 如果是多表 JOIN,会列出多个行

4️⃣ type访问类型(最重要!)

表示 MySQL 如何查找行,性能从最优到最差:

type 含义 性能 是否可接受
system 表只有一行(系统表) ⚡ 极快
const 通过主键或唯一索引查一行 ⚡ 快
eq_ref 主键/唯一索引 JOIN ⚡ 快
ref 非唯一索引等值查询 ✅ 好
range 范围扫描(如 BETWEEN, >, IN) ✅ 可接受
index 全索引扫描(遍历索引树) ⚠️ 慢 ❌ 尽量避免
ALL 全表扫描 💥 极慢 ❌ 必须优化!

🔥 黄金法则:生产环境绝不能出现 ALL

在我们的例子中:type=ref → 用了非唯一索引等值查询,OK!


5️⃣ possible_keys:可能用到的索引

  • MySQL 认为可以使用的索引列表
  • 如果为 NULL,说明没有可用索引

在例子中:possible_keys = idx_user → 只有这个索引可选。


6️⃣ key实际使用的索引(关键!)

  • MySQL 最终选择的索引
  • 如果为 NULL,说明没走索引(全表扫描)

✅ 我们看到 key = idx_user索引用上了!

⚠️ 但如果 possible_keys 有值,而 keyNULL,说明优化器认为走索引不如全表扫描快(比如低区分度字段)。


7️⃣ key_len:索引使用长度(字节)

  • 表示索引中被使用部分的字节数
  • 可用来判断联合索引用了几列
计算规则(InnoDB):
类型 长度
BIGINT 8 字节
INT 4 字节
DATETIME 5 字节
VARCHAR(n) (UTF8MB4) n*4 + 2(可变长需额外 2 字节)
允许 NULL +1 字节

在我们的例子:

  • user_idBIGINT NOT NULL → 8 字节
  • key_len = 8只用了联合索引的第一列(如果有的话)

✅ 如果建了联合索引 (user_id, order_status),但 key_len=8,说明第二列没用上


8️⃣ rows:预估扫描行数

  • MySQL 估计需要扫描的行数
  • 不是实际结果行数!
  • 越小越好(理想是几十,别上万)

在例子中:rows=50 → 扫描 50 行,很好!

❌ 如果 rows=500000,即使 type=ref,也可能慢!


9️⃣ Extra:额外信息(非常重要!)

常见值及含义:

Extra 值 含义 建议
Using where 在存储引擎返回后,Server 层再过滤 ✅ 正常(如多条件查,只一个走索引)
Using index 覆盖索引!无需回表 ✅ 极佳!
Using index condition 索引条件下推(ICP) ✅ 好
Using filesort 需要额外排序(没用索引排序) ❌ 必须优化!
Using temporary 用了临时表(如 GROUP BY 无索引) ❌ 严重性能问题!
Using join buffer JOIN 时用了缓冲区(没索引) ❌ 需优化 JOIN 条件

💥 看到 Using filesortUsing temporary,立刻警惕!


三、Spring Boot 中如何使用 EXPLAIN?

方法 1:直接在数据库客户端执行(推荐开发阶段)

EXPLAIN SELECT * FROM order_info WHERE user_id = 1001;

方法 2:在 MyBatis Mapper 中写(仅调试用)

@Mapper
public interface OrderMapper {
    
    @Select("EXPLAIN SELECT * FROM order_info WHERE user_id = #{userId}")
    List<Map<String, Object>> explainFindByUserId(@Param("userId") Long userId);
}

然后在 Service 中调用:

@Service
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;

    public void debugQuery(Long userId) {
        List<Map<String, Object>> plan = orderMapper.explainFindByUserId(userId);
        plan.forEach(System.out::println);
        // 输出:{id=1, select_type=SIMPLE, table=order_info, type=ref, ...}
    }
}

⚠️ 注意:不要在生产接口中调用 EXPLAIN,仅用于本地调试或压测分析。


四、实战案例:从“慢查询”到“毫秒级”

❌ 问题 SQL

SELECT * FROM order_info 
WHERE order_status = 1 
ORDER BY create_time DESC 
LIMIT 10;

EXPLAIN 结果:

type key rows Extra
ALL NULL 1000000 Using where; Using filesort

💥 问题:

  • 全表扫描(type=ALL
  • 额外排序(Using filesort

✅ 优化:建联合索引

-- 覆盖查询 + 排序
CREATE INDEX idx_status_time ON order_info(order_status, create_time);

再次 EXPLAIN:

type key rows Extra
range idx_status_time 700000 Using index

✅ 效果:

  • 走索引范围扫描
  • Using index → 覆盖索引,无需回表
  • 自动按 create_time 排序,无需 filesort

查询从 2 秒 → 20 毫秒!


五、常见误区 & 注意事项

❌ 误区 1:“有索引就一定快”

→ 错!如果 type=ALLrows 很大,索引可能没用上。

❌ 误区 2:“EXPLAIN 的 rows 是实际结果数”

→ 错!它是基于统计信息的估算值,可能不准(可通过 ANALYZE TABLE 更新统计)。

✅ 建议 1:高频查询必须 EXPLAIN

上线前对核心 SQL 执行 EXPLAIN,确保:

  • type 不是 ALL
  • key 不是 NULL
  • Extra 没有 filesort / temporary

✅ 建议 2:善用覆盖索引

SELECT 的字段也放进索引,避免回表:

-- 查询只用索引就能完成
CREATE INDEX idx_cover ON order_info(user_id, create_time, amount);
SELECT create_time, amount FROM order_info WHERE user_id = 1001;

六、总结:EXPLAIN 速查表

字段 关注点
type 访问类型 const/ref/range index/ALL
key 实际索引 非 NULL NULL
rows 扫描行数 < 1000 > 10000
Extra 额外操作 Using index filesort/temporary

记住:EXPLAIN 不是魔法,而是你和 MySQL 对话的桥梁。看懂它,你就掌握了 SQL 性能调优的钥匙!


视频看了几百小时还迷糊?关注我,几分钟让你秒懂!

Logo

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

更多推荐