Featured image of post MySQL 实战速查:EXPLAIN 全字段解读 + 经典 SQL 题解

MySQL 实战速查:EXPLAIN 全字段解读 + 经典 SQL 题解

本文系统整理 EXPLAIN 各列的含义、type 性能等级、key_len 计算规则、Extra 关键提示,并附 9 道高频 SQL 笔试题(学生选课表、行转列、第 N 高、相关子查询)的标准解法。

为什么写这篇:EXPLAIN 是 MySQL 调优的"听诊器",但官方文档对 key_lenfilteredExtra 这些字段解释过于简略。本文用真实 SQL 示例把每个字段的含义讲透,并配 9 道 SQL 笔试题的标准解法——这些题在笔试/面试中重复率极高。

适用读者:需要做 SQL 优化的后端、面试准备、想系统性梳理 SQL 语法的同学。

前置知识:会基本 SQL,了解索引概念。

目录

  1. EXPLAIN 基础:一个完整案例
  2. EXPLAIN 字段逐列解读
  3. type 性能等级排序
  4. key_len 的字节数计算规则
  5. Extra 常见提示与优化方向
  6. 9 道经典 SQL 笔试题

1. EXPLAIN 基础:一个完整案例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
EXPLAIN
SELECT F_LogId AS logId, F_CategoryId AS categoryId,
       F_SourceObjectId AS sourceObjectId,
       F_SourceContentJson AS sourceContentJson,
       F_OperateTime AS operateTime,
       F_OperateUserId AS operateUserId
FROM base_log
WHERE F_DeleteMark = 0
  AND (F_DeleteMark = 0)
ORDER BY F_OperateTime DESC
LIMIT 15;

第一次 EXPLAIN 输出

idselect_typetablepartitionstypepossible_keyskeykey_lenrefrowsfilteredExtra
1SIMPLEbase_logALL132000010.00Using where; Using filesort

type=ALL 是全表扫描。rows=1320000 表示扫描 132 万行。Using filesort 表示额外排序。

优化一:加索引

1
CREATE INDEX idx_OperateTime ON base_log (F_OperateTime);

第二次 EXPLAIN 输出

idselect_typetabletypekeykey_lenrowsExtra
1SIMPLEbase_logindexidx_OperateTime815Using where; Backward index scan
  • typeALL 升级到 index(全索引扫描)
  • rows 从 132 万降到 15(只用 LIMIT 15 即可)
  • Extra 出现 Backward index scan(对 F_OperateTime 倒序扫,命中 ORDER BY DESC

结论:单列索引 + LIMIT 优化,让 132 万行扫描变成 15 行扫描,性能从秒级降到毫秒级。


2. EXPLAIN 字段逐列解读

字段含义
idSELECT 标识符。同一个 SELECT 共享 id;子查询/UNION 会递增
select_type查询类型(SIMPLE / PRIMARY / UNION / SUBQUERY / DEPENDENT SUBQUERY)
table当前行访问的表
partitions匹配的分区(如果表是分区表)
type最重要:join 类型,决定性能等级
possible_keys优化器考虑使用的索引(不一定会用)
key实际使用的索引
key_len索引使用的字节数(可判断复合索引用了几个字段)
ref哪个字段或常数与 key 一起被使用
rows优化器估算要扫描的行数(实际可能偏差巨大)
filtered过滤后剩余行数百分比
Extra额外信息(Using filesort / Using temporary / Using index 等)

2.1 select_type 常见取值

  • SIMPLE:不含 UNION 和子查询的最简单查询
  • PRIMARY:最外层 SELECT
  • UNION:UNION 的第二或随后的查询
  • SUBQUERY:子查询中的第一个 SELECT
  • DEPENDENT SUBQUERY:依赖外层查询结果的子查询(通常很慢

3. type 性能等级排序

最差到最好

1
ALL < index < range ~ index_merge < ref < eq_ref < const < system
type含义性能
system表中只有 1 条数据最佳
const主键/唯一索引等值扫描,最多 1 行极佳
eq_ref多表 JOIN,每个前表结果只匹配后表 1 行很好
ref非唯一索引等值扫描,或最左前缀命中
range索引范围扫描(=、<、>、BETWEEN、IN)
index全索引扫描(不读数据文件)较差
ALL全表扫描最差

底线:你的 SQL 不应该出现 type=ALL——除非表只有几十行,或者确实没有合适的过滤条件。


4. key_len 的字节数计算规则

key_len 能告诉你复合索引里实际用到了几个字段

4.1 基础计算公式

字符串

  • char(n)n 字节
  • varchar(n)
    • utf8 编码:3 * n + 2 字节(2 是长度前缀)
    • utf8mb4 编码:4 * n + 2 字节

数值

类型字节
TINYINT1
SMALLINT2
MEDIUMINT3
INT4
BIGINT8

时间

类型字节
DATE3
TIMESTAMP4
DATETIME8

字段属性:如果字段允许 NULL多算 1 字节

4.2 实战算例

1
KEY idx_name_age (name varchar(50), age int, dept varchar(20))

索引长度计算:

  • name varchar(50) utf8mb450 * 4 + 2 + 1 = 203(含 NULL 标志)
  • age int NOT NULL4
  • dept varchar(20) utf8mb420 * 4 + 2 + 1 = 83
SQL 条件命中索引key_len
WHERE name = 'a'name203
WHERE name = 'a' AND age = 10name + age207
WHERE name = 'a' AND age = 10 AND dept = 'b'全部290
WHERE age = 10NULL(不满足最左前缀)

关键洞察key_len 越大说明用到的索引字段越多。如果 key_len 跟你期望的不一致,往往是漏写了等值条件或写错了列顺序


5. Extra 常见提示与优化方向

Extra含义优化方向
Using filesort额外的排序操作(不能用索引顺序)让 ORDER BY 命中复合索引顺序
Using temporary使用了临时表(GROUP BY / DISTINCT)让 GROUP BY 命中索引
Using index覆盖索引:只在索引里取数据,不回表目标
Backward index scan倒序索引扫描通常对 ORDER BY DESC 是好事
Using where存在 WHERE 子句筛选配合 type 看
Using index condition索引下推(ICP,5.6+)通常是好事
Impossible WHEREWHERE 永远为 false删掉这种 SQL

6. 9 道经典 SQL 笔试题

表结构(学生选课经典案例):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
CREATE TABLE student (
    id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    name CHAR(10) NOT NULL
);
INSERT INTO student(name) VALUES ('张三'),('李四');

CREATE TABLE course (
    id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    name CHAR(20) NOT NULL
);
INSERT INTO course(name) VALUES ('语文'),('数学');

CREATE TABLE student_course (
    sid INT UNSIGNED,
    cid INT UNSIGNED,
    score INT UNSIGNED NOT NULL,
    FOREIGN KEY (sid) REFERENCES student(id),
    FOREIGN KEY (cid) REFERENCES course(id),
    PRIMARY KEY(sid, cid)
);
INSERT INTO student_course VALUES (1,1,80),(1,2,90),(2,1,90),(2,2,70);

6.1 查重名学生

1
2
3
4
5
6
SELECT id, name
FROM student
WHERE name IN (
    SELECT name FROM student GROUP BY name HAVING COUNT(*) > 1
)
ORDER BY name;

关系型数据库查重复行的通用套路:GROUP BY + HAVING COUNT > 1 + 子查询。

6.2 平均分不及格的学生

1
2
3
4
SELECT sid, AVG(score) AS avg_score
FROM student_course
GROUP BY sid
HAVING avg_score < 60;

WHERE 过滤记录HAVING 过滤分组。聚集函数(AVG/COUNT/SUM)只能放 HAVING 里。

6.3 每门课都不低于 80 分的学生

1
2
3
4
5
SELECT DISTINCT sid
FROM student_course
WHERE sid NOT IN (
    SELECT sid FROM student_course WHERE score < 80
);

反向思维:要求"所有都 >= 80"等价于"不存在 < 80 的"。这是数理逻辑里的 ∀x:P ≡ ¬∃x:¬P

6.4 每个学生的总成绩(含没成绩的)

1
2
3
4
SELECT name, SUM(score) AS total
FROM student
    LEFT JOIN student_course ON student.id = student_course.sid
GROUP BY sid;

INNER JOIN 会过滤掉没成绩的学生。用 LEFT JOIN 才能保留。

6.5 总成绩最高的学生

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
-- 标准解法
SELECT sid, SUM(score) AS sum_score
FROM student_course
GROUP BY sid
ORDER BY sum_score DESC
LIMIT 1;

-- 错误解法(虽然能跑,但语义反了)
SELECT sid, SUM(score) AS sum_score
FROM student_course
GROUP BY sid
HAVING sum_score >= ALL (
    SELECT SUM(score) FROM student_course GROUP BY sid
);

6.6 第 N 高的分数

1
2
3
4
5
6
7
-- 课程 1 第 2 高的分数
SELECT score
FROM student_course
WHERE cid = 1
GROUP BY score
ORDER BY score DESC
LIMIT 1, 1;  -- offset=1, count=1

常见错误LIMIT 2 然后 MIN(score)——因为执行顺序是 GROUP BY → MIN → ORDER BY → LIMIT,MIN 后 ORDER BY 已经无意义。

正确做法是用 LIMIT offset, count 跳过的语法。

6.7 每门课最高分的学生(核心

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
-- 错误写法(MySQL 不会报错,但 sid 是无效的)
SELECT sid, cid, MAX(score)
FROM student_course
GROUP BY cid;

-- 正确写法:相关子查询
SELECT *
FROM student_course AS x
WHERE score >= (
    SELECT MAX(score) FROM student_course AS y WHERE cid = x.cid
);

GROUP BY 后的 SELECT 字段只能是:① GROUP BY 字段;② 聚集函数;③ 每个分组内均相同的字段。MySQL 的扩展语法(sid)默认开了 ONLY_FULL_GROUP_BY 是会拒绝的。

6.8 每门课的前 N 名

1
2
3
4
5
6
7
8
9
-- 每门课前 2 名
SELECT *
FROM student_course AS x
WHERE 2 > (
    SELECT COUNT(*)
    FROM student_course AS y
    WHERE y.cid = x.cid AND y.score > x.score
)
ORDER BY cid, score DESC;

核心思想:对于某个分数,如果同课程下比它高的分数少于 N 个,那么它就是前 N 名之一。

6.9 比赛两两组合

1
2
3
4
-- 4 队两两比赛,列出所有可能的比赛组合
SELECT a.name, b.name
FROM team a, team b
WHERE a.name < b.name;

自连接 + 不等式:本质是 team 表和自己做笛卡尔积,再过滤掉 a=ba>b 的重复。


使用 Hugo 构建
主题 StackJimmy 设计