本页内容受版权保护 · 已添加水印 · 禁止任何形式转载
索引(B+ 树 / 覆盖 / 失效 / 优化) · 索引失效与执行计划

EXPLAIN 输出的关键字段(type, key, key_len, rows, Extra)各自含义?

What do the key fields (type, key, key_len, rows, Extra) in the

答案语言

考察要点

这道题考察的是候选人对 MySQL 查询优化的核心能力——解读执行计划。面试官想看你是否具备以下能力:

  1. 诊断能力:能否通过 EXPLAIN 的输出,快速定位 SQL 查询的性能瓶颈。
  2. 底层理解:是否深刻理解索引如何工作,以及查询优化器(Optimizer)是如何选择索引和执行路径的。
  3. 优化实践:能否根据诊断结果,提出具体、有效的 SQL 或索引优化方案。

简单说,这不仅仅是背诵字段含义,而是考察你利用工具解决实际性能问题的思维和经验。

核心概念

EXPLAIN 用于显示 MySQL 如何执行一条 SQL 语句。以下是其输出中最重要的几个字段的详细解读。

假设我们有这样一张表:

sql
1CREATE TABLE `employees` (
2 `id` int(11) NOT NULL AUTO_INCREMENT,
3 `emp_no` int(11) NOT NULL,
4 `name` varchar(50) NOT NULL,
5 `dept_no` char(4) NOT NULL,
6 `hire_date` date DEFAULT NULL,
7 PRIMARY KEY (`id`),
8 UNIQUE KEY `uk_emp_no` (`emp_no`),
9 KEY `idx_dept_no_name` (`dept_no`, `name`)
10) ENGINE=InnoDB;

1. type (访问类型)

这是 EXPLAIN 输出中最关键的字段,描述了 MySQL 找到所需数据的方式。性能从优到劣依次为:

type含义性能评级示例
system表中只有一行数据(系统表),是 const 的特例。⭐⭐⭐⭐⭐ (最优)SELECT * FROM mysql.proxies_priv WHERE host='localhost';
const通过主键或唯一索引,最多只匹配一行数据。查询极快。⭐⭐⭐⭐⭐SELECT * FROM employees WHERE id = 10;
eq_refJOIN 查询中,驱动表(前表)的每一行,在被驱动表(后表)中都只通过主键或唯一索引精确匹配一行。⭐⭐⭐⭐⭐SELECT * FROM t1 JOIN t2 ON t1.id = t2.t1_id; (t2.t1_id 是主键/唯一)
ref使用非唯一性索引或唯一索引的前缀,返回匹配某个值的所有行。⭐⭐⭐⭐ (优秀)SELECT * FROM employees WHERE dept_no = 'd005'; (使用 idx_dept_no_name)
range使用索引进行范围查询。常见于 BETWEEN, >, <, IN() 等操作。⭐⭐⭐ (良好)SELECT * FROM employees WHERE emp_no > 10000; (使用 uk_emp_no)
index全索引扫描 (Full Index Scan)。遍历整个索引树来查找数据。⭐⭐ (较差)SELECT emp_no FROM employees; (emp_no 有索引,查询只需访问索引)
ALL全表扫描 (Full Table Scan)。遍历整张表来查找匹配的行。⭐ (最差)SELECT * FROM employees WHERE name = 'John Doe'; (name 列无索引)

优化目标:至少要达到 range 级别,最好是 ref 或 eq_ref。出现 ALL 通常意味着需要添加索引。


2. key

表示 MySQL 实际决定使用的索引。如果为 NULL,则表示没有使用索引。

  • possible_keys 字段列出了 MySQL 认为可能可以使用的索引,但 key 才是最终的选择。
  • 如果 possible_keys 有值而 key 为 NULL,通常是因为优化器计算后认为全表扫描比走索引(可能伴随大量回表)更快。

3. key_len (索引长度)

表示本次查询中,所使用的索引的字节数。这个值可以帮助我们判断联合索引是否被完全利用。key_len 越短,通常意味着索引效率越高。

其计算规则如下:

  1. 数据类型基础长度:
    • INT: 4 字节
    • BIGINT: 8 字节
    • DATE: 3 字节
    • DATETIME: 5 字节 (MySQL 5.6+)
  2. 变长类型 (VARCHAR, TEXT):
    • 需额外 1 或 2 字节记录长度。
    • 字符集影响:latin1 每个字符 1 字节,gbk 是 2 字节,utf8 是 3 字节,utf8mb4 是 4 字节。
    • 公式:M * 字符集字节数 + 变长字段长度记录字节数
  3. NULLable:
    • 如果字段允许为 NULL,则需要额外 1 字节作为 NULL 标记。

示例:对于 idx_dept_no_name (dept_no CHAR(4), name VARCHAR(50)),字符集为 utf8mb4,且都 NOT NULL。

  • WHERE dept_no = 'd005':
    • key_len = 4 (CHAR(4)) * 4 (utf8mb4) = 16
  • WHERE dept_no = 'd005' AND name = 'Tom':
    • key_len = 16 (dept_no部分) + 50 (VARCHAR(50)) * 4 (utf8mb4) + 2 (变长记录) = 16 + 200 + 2 = 218
    • 通过观察 key_len 的值,可以精确判断联合索引有几个部分被用到了。

4. rows

MySQL 优化器估算的,为找到所需结果集而必须读取的行数。这是一个估算值,不是精确值。

  • 这个数字越小越好。
  • 如果 rows 值远大于实际返回的行数,可能意味着索引选择性差,或者表的统计信息陈旧(可尝试 ANALYZE TABLE your_table; 来更新统计信息)。

5. Extra (额外信息)

包含了不适合在其他列中显示的、但十分重要的额外信息。

Extra 值含义性能评级
Using index覆盖索引 (Covering Index)。查询所需的所有列都直接从索引中获取,无需回表查询主键索引,性能极高。✅ 极好
Using index condition索引条件下推 (Index Condition Pushdown, ICP)。在索引层面就对 WHERE 条件进行过滤,减少了回表次数和 Server 层的数据传输。MySQL 5.6+ 的重要优化。✅ 好
Using where在存储引擎层返回数据后,MySQL Server 层再使用 WHERE 子句进行行过滤。很常见,但如果伴随 type=ALL,就是性能问题的信号。⚠️ 中性/警惕
Using temporaryMySQL 需要创建一个内部临时表来处理查询。通常发生在 GROUP BY 和 ORDER BY 的列不一致时。严重影响性能。❌ 差
Using filesortMySQL 无法利用索引完成排序,不得不在内存或磁盘上进行外部排序。数据量大时非常慢。❌ 差
Using join bufferJOIN 操作没有使用索引,MySQL 不得不使用一块缓冲区来辅助完成连接。❌ 差

常见低分回答

  1. 混淆 Using index 和 type=index:
    • 错误描述:"只要用了索引,Extra 里就会显示 Using index"。
    • 正解:Using index 特指覆盖索引,是最高效的索引使用方式之一。而 type=index 是指全索引扫描,虽然也走了索引,但效率远低于通过索引精确定位数据。
  2. 认为 rows 是精确值:
    • 错误描述:"这条 SQL 扫描了 10000 行"。
    • 正解:rows 是优化器的估算值,受表统计信息影响,可能与实际扫描行数有较大偏差。要获取精确值,应使用 EXPLAIN ANALYZE (MySQL 8.0+) 或查看慢查询日志。
  3. 看到 Using where 就认为是坏事:
    • 错误描述:"Extra 里有 Using where,说明 SQL 有问题"。
    • 正解:Using where 本身是中性的,表示 Server 层进行了数据过滤。关键要看它和哪个 type 组合。type=ref + Using where 是正常的。但 type=ALL + Using where 则表示在全表扫描的基础上进行过滤,是典型性能瓶颈。
  4. 对 key_len 的理解浮于表面:
    • 错误描述:"key_len 越大,用的索引越多,越好"。
    • 正解:key_len 让我们能精确判断联合索引的利用情况。例如,一个 (a, b, c) 的联合索引,如果 key_len 只等于 a 列的长度,说明只有索引的最左前缀被利用了,b 和 c 并没有在查找中起作用。
  5. 对 type 的性能排序不清晰:
    • 错误描述:"只要 type 不是 ALL 就行了"。
    • 正解:type 的不同值之间性能差异巨大。index(全索引扫描)和 range(范围扫描)虽然比 ALL 好,但在大数据量下仍然可能很慢。优化目标应该是将 type 提升到 ref、eq_ref 或 const。

工程实践与优化

  • 核心优化思路:通过 EXPLAIN 分析,我们的目标是让 type 尽可能好(ref 或更优),让 rows 尽可能小,并力求在 Extra 中看到 Using index(覆盖索引)。同时,要全力避免 Using temporary 和 Using filesort。

  • 实战案例:优化一个排序查询

    • 慢查询:SELECT emp_no, name, hire_date FROM employees WHERE dept_no = 'd005' ORDER BY hire_date;
    • EXPLAIN 可能的输出:
      • type: ref (因为 dept_no 有索引 idx_dept_no_name)
      • key: idx_dept_no_name
      • key_len: 16 (只用了 dept_no 部分)
      • Extra: Using where; Using filesort (因为 idx_dept_no_name 不包含 hire_date,无法利用索引排序)
    • 优化方案:创建或修改索引,使其满足最左前缀原则并包含排序字段。
      • ALTER TABLE employees ADD INDEX idx_dept_hire (dept_no, hire_date);
    • 优化后的 EXPLAIN:
      • type: ref
      • key: idx_dept_hire
      • key_len: 16
      • Extra: Using index condition (或 Using where),Using filesort 消失了。因为现在可以利用 idx_dept_hire 索引,直接按 hire_date 的顺序读取数据,无需额外排序。
  • 诊断工具:

    • EXPLAIN:快速获取估算的执行计划。
    • EXPLAIN ANALYZE (MySQL 8.0+):执行查询并返回真实的执行计划、耗时和行数,信息更精确,是优化的终极武器。
    • pt-query-digest:分析慢查询日志,找出最需要优化的查询。

常见追问与延伸题目

  1. 追问:EXPLAIN 显示 type=range,但查询还是很慢,可能是什么原因?怎么优化?

    • 回答要点 1: 范围过大导致回表过多。range 扫描的范围如果覆盖了表中大部分数据(如 status > 0,而 99% 的 status 都大于 0),优化器虽然用了索引,但需要对索引扫描出的每一行都进行一次回表(回到主键索引去取其他列的数据)。大量的随机 I/O 会导致性能急剧下降,甚至比全表扫描的顺序 I/O 更慢。
    • 回答要点 2: 优化方案是建立覆盖索引。将查询所需的所有列(SELECT 和 WHERE 中用到的)都包含在一个索引里。这样,Extra 字段会显示 Using index,查询只需扫描这个索引即可,完全避免了回表,性能会得到巨大提升。
  2. 追问:Using index 和 Using index condition (ICP) 有什么本质区别?

    • 回答要点 1: Using index (覆盖索引) 是终点。它意味着查询需要的所有数据都在索引里,存储引擎层(如 InnoDB)直接返回结果给客户端,MySQL Server 层无需任何额外操作。这是最高效的模式。
    • 回答要点 2: Using index condition (ICP) 是过程优化。它发生在索引还不能完全覆盖查询时。在 MySQL 5.6 之前,存储引擎只能用索引键来定位行,然后把整行数据都返回给 Server 层,Server 层再用 WHERE 的其他条件过滤。ICP 允许 Server 层将部分 WHERE 条件下推给存储引擎,存储引擎在遍历索引时就用这些条件进行过滤,只将符合条件的行(的完整数据)返回给 Server 层。本质区别在于:ICP 减少了存储引擎需要返回给 Server 层的数据量,从而减少了回表次数和 I/O,但它仍然需要回表;而覆盖索引则完全消灭了回表。
  3. 追问:如果 possible_keys 有多个索引,MySQL 优化器是如何选择最终的 key 的?

    • 回答要点 1: 基于成本的优化(Cost-Based Optimization, CBO)。MySQL 优化器会估算使用每个可能索引的“成本”,并选择成本最低的那个。
    • 回答要点 2: 成本估算考虑的因素主要包括:
      • 扫描行数:通过索引的统计信息(Cardinality)估算需要扫描的行数。Cardinality 越高,索引选择性越好,越可能被选中。
      • 是否需要回表:如果一个索引是覆盖索引,它的成本会大大降低。
      • 是否需要排序:如果一个索引能避免 filesort,它的成本也会降低。
      • I/O 成本和 CPU 成本:综合估算读取数据页的 I/O 成本和处理数据的 CPU 成本。
    • 回答要点 3: 统计信息的重要性。优化器的选择严重依赖于表的统计信息。如果统计信息不准(例如,表经过大量增删改后没有 ANALYZE),优化器就可能做出非常糟糕的选择。这也是为什么有时需要用 FORCE INDEX 来强制指定索引的原因。