RSS
菜单
全部文章快讯开发科技深度热点

第 1 章 索引基础面试精讲

内容摘要

索引是 MySQL 面试的第一高频主题,几乎每一场面试都会从索引问起。本篇用 16 道高频面试题串起索引的全部基础考点:索引是什么、B+ 树底层原理、聚簇与非聚簇索引、回表与覆盖索引、最左前缀、索引代价等。学完本篇,你能完整、有条理地回答"索引"类基础问题,并接住面试官的层层追问。前置知识:会写基本的建表与 SELECT;本系列示例基于 MySQL 8.0。

📖 本篇简介:索引是 MySQL 面试的第一高频主题,几乎每一场面试都会从索引问起。本篇用 16 道高频面试题串起索引的全部基础考点:索引是什么、B+ 树底层原理、聚簇与非聚簇索引、回表与覆盖索引、最左前缀、索引代价等。学完本篇,你能完整、有条理地回答"索引"类基础问题,并接住面试官的层层追问。前置知识:会写基本的建表与 SELECT;本系列示例基于 MySQL 8.0。

目录

  • 1 索引高频考点地图
  • 题目 1 什么是索引底层为什么用 B+ 树
  • 题目 2 聚簇索引与非聚簇索引的区别
  • 题目 3 主键索引唯一索引普通索引的区别
  • 题目 4 为什么 InnoDB 主键推荐用自增整数
  • 题目 5 什么是回表什么是覆盖索引
  • 题目 6 联合索引的最左前缀原则是什么
  • 题目 7 一个 SQL 只能用一个索引吗
  • 题目 8 索引能提升所有查询性能吗
  • 题目 9 哈希索引和 B+ 树索引的区别
  • 题目 10 全文索引与普通索引的区别
  • 题目 11 索引下推是什么
  • 题目 12 为什么建议索引选择性高的列
  • 题目 13 NULL 值对索引的影响
  • 题目 14 冗余索引与重复索引有什么区别
  • 题目 15 为什么 B+ 树索引能扛住千万级数据
  • 题目 16 MySQL 怎么决定要不要用索引
  • 复习清单
  • 练习题

1 索引高频考点地图

面试里"索引"类问题的常见问法,按难度分层:

| 难度 | 典型问题 | 本篇对应 |

|---|---|---|

| 入门 | 索引是什么?底层结构? | 题目 1 |

| 基础 | 聚簇/非聚簇?回表?覆盖索引? | 题目 2、5 |

| 进阶 | 最左前缀?索引下推?代价估算? | 题目 6、11、16 |

| 实战 | 主键选型?NULL?冗余索引? | 题目 4、13、14 |

一条主线记住:索引是"有序的数据结构 + 减少回表"的折中。所有追问都围绕"它怎么帮你少读数据、以及它为此付出了什么代价"展开。

题目 1:什么是索引?底层为什么用 B+ 树?

考点

  • 索引的定义与本质:加速查询的数据结构;
  • B+ 树与二叉树、B 树、哈希表的对比;
  • InnoDB 索引页的组织方式。

参考答案

  • 索引是对表中一列或多列的值进行排序的一种结构,目的就是减少扫描的数据量,让查找从"全表逐行"变成"沿树定位"。
  • 底层用 B+ 树,而不是二叉树、B 树或哈希表:

1. 二叉树在数据量大时树高过高,且可能退化成链表,磁盘 IO 次数爆炸;

2. B 树每个节点既存索引又存数据,节点更大、单页能存的键更少,且非叶子节点也能命中,查找路径不稳定;

3. 哈希索引只能等值匹配,不支持范围查询和排序;

4. B+ 树只有叶子节点存数据,非叶子节点能存更多键(树更矮),且叶子节点之间用链表相连,天然支持范围查询和排序。

  • InnoDB 默认页大小 16KB,一个 3 层的 B+ 树能存约千万到上亿行,3 次磁盘 IO 就能定位目标。
-- 验证索引效果:建索引前后查询计划对比(MySQL 8.0)
EXPLAIN SELECT * FROM user WHERE username = 'zhangsan';
-- type 从 ALL(全表扫描)变成 ref(走索引)

追问

  • 问:B+ 树和 B 树在范围查询上谁更强?答:B+ 树叶子节点链表有序,范围查询只需定位起点后顺序遍历;B 树中序遍历会频繁在父子节点间跳转。
  • 问:为什么不用跳表?答:跳表也是有序结构,但每个节点冗余指针多、缓存不友好,数据库领域 B+ 树是工程实践的主流选择。

题目 2:聚簇索引与非聚簇索引的区别?

考点

  • 聚簇索引 = 主键索引,叶子存整行数据;
  • 非聚簇索引(二级索引)叶子存主键值;
  • 一张表只能有一个聚簇索引。

参考答案

  • 聚簇索引(Clustered Index):InnoDB 中主键索引就是聚簇索引,叶子节点直接存放整行数据。表数据物理上按主键顺序组织。
  • 非聚簇索引(Secondary Index,二级索引):叶子节点存放的是主键值(不是行数据)。通过二级索引查找,要先在索引树上找到主键,再回聚簇索引取行——这就是回表。
  • 一张表只有一个聚簇索引(数据只有一份物理排列),但可以有多个二级索引。
  • 引申:没有显式主键时,InnoDB 会找第一个非空唯一索引作为聚簇索引;都没有,则生成隐藏的 ROWID 作为聚簇索引。
-- 演示:二级索引的叶子存主键值,查询需要回表
CREATE TABLE t_user (
  id   BIGINT PRIMARY KEY,          -- 聚簇索引,叶子存整行
  name VARCHAR(50),
  KEY idx_name (name)               -- 二级索引,叶子存 id
) ENGINE=InnoDB;

EXPLAIN SELECT * FROM t_user WHERE name = 'tom';
-- 走 idx_name(type=ref),Extra 若出现 Using index condition 说明有下推;无覆盖则回表

追问

  • 问:如果二级索引能覆盖查询的所有列,还需要回表吗?答:不需要,这就是覆盖索引,Extra 显示 Using index。
  • 问:主键改了会怎样?答:所有二级索引里存的主键值都要跟着改,这也是"主键越短越好"的原因。

题目 3:主键索引、唯一索引、普通索引的区别?

考点

  • 三种索引的约束能力;
  • 唯一性与 NULL 的处理差异。

参考答案

| 类型 | 唯一性 | NULL | 用途 |

|---|---|---|---|

| 主键索引 PRIMARY KEY | 唯一且非空 | 不允许 | 聚簇索引,表身份 |

| 唯一索引 UNIQUE KEY | 唯一 | 允许多个 NULL | 防止业务字段重复(手机号、订单号) |

| 普通索引 KEY/INDEX | 不限制 | 允许 | 加速查询 |

  • 主键索引是非空唯一,一张表只有一个;唯一索引允许多个 NULL(MySQL 认为 NULL 互不相等,UNIQUE 列可存多个 NULL)。
  • 唯一索引不仅能加速查询,还能在应用层漏判时兜底防重(写入重复会报错)。
-- 演示唯一索引兜底:重复插入报错
CREATE TABLE t_account (
  id      BIGINT PRIMARY KEY,
  card_no CHAR(18) UNIQUE            -- 身份证号唯一
);
INSERT INTO t_account VALUES (1, '110101199001010011');
-- 再次插入相同 card_no 会报错:Duplicate entry '110101199001010011' for key 'card_no'

追问

  • 问:唯一索引列存了 NULL,还能再存一个 NULL 吗?答:可以,MySQL 的唯一索引允许多个 NULL(8.0 语义如此,与标准 SQL 有差异,值得在面试中主动点出)。
  • 问:主键能用 UUID 吗?答:能用但很不推荐,见题目 4。

题目 4:为什么 InnoDB 主键推荐用自增整数?

考点

  • 自增主键对 B+ 树插入顺序的影响;
  • 页分裂与碎片;
  • UUID 的随机性代价。

参考答案

  • 自增主键插入时数据追加在 B+ 树最右叶子节点,顺序写,几乎不发生页分裂,写入性能高且无碎片;
  • UUID/随机主键导致数据随机散落,频繁触发页分裂、页重组,写入变慢,索引膨胀(UUID 是 16 字节,比 BIGINT 的 8 字节大),二级索引里存的键也更大;
  • 数字主键排序、比较、范围查询都更高效,还能方便地做分库分表的全局 ID 设计;
  • 因此面试标准答案:自增 BIGINT 主键是 InnoDB 表的最佳实践(有业务含义的列如订单号也可以,但尽量不要用随机值)。
-- 推荐写法:自增 BIGINT 无符号主键
CREATE TABLE t_order (
  id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '订单ID',
  order_no VARCHAR(32) NOT NULL COMMENT '订单号',
  UNIQUE KEY uk_order_no (order_no)    -- 业务唯一靠唯一索引
) ENGINE=InnoDB;

追问

  • 问:自增主键真的不会浪费吗?答:中间被删除的行不会复用 ID(默认 AUTO_INCREMENT 不回溯),这是特性不是 bug;批量插入可用 innodb_autoinc_lock_mode 控制锁粒度。
  • 问:分布式场景自增会撞吗?答:会,所以分布式一般用雪花 ID 或号段发号,本地库内仍按自增/雪花做主键。

题目 5:什么是回表?什么是覆盖索引?

考点

  • 回表的定义与代价;
  • 覆盖索引如何消除回表;
  • Explain 中 Using index 的含义。

参考答案

  • 回表:二级索引只存主键值,用二级索引查到主键后,还要再回聚簇索引查整行,这第二次查找就叫回表。回表多一次随机 IO。
  • 覆盖索引:查询所需的所有列都包含在同一个二级索引里,不需要回表,Extra 显示 Using index。
  • 优化手段:把"查询列 + WHERE 条件列 + ORDER BY 列"设计进同一个联合索引,让查询"用索引即可完成"。
-- 回表 vs 覆盖索引对比
CREATE TABLE t_user (
  id BIGINT PRIMARY KEY,
  name VARCHAR(50),
  age  INT,
  KEY idx_name_age (name, age)
);

-- 情况一:要的列只有 id(索引里有),Extra = Using index,不回表
EXPLAIN SELECT id, name FROM t_user WHERE name = 'tom';

-- 情况二:要 age(联合索引第二列,也在索引内),仍不回表
EXPLAIN SELECT id, name, age FROM t_user WHERE name = 'tom';

-- 情况三:要多查一个索引外的列(如 phone),必须回表
EXPLAIN SELECT id, name, phone FROM t_user WHERE name = 'tom';

追问

  • 问:覆盖索引是不是越多越好?答:不是,联合索引本身有维护成本,覆盖索引是"查询模式稳定"时才值得设计。
  • 问:Using index condition 和 Using index 有什么区别?答:前者是索引下推(回表前先过滤),后者是完全覆盖不回表,两者不同,别答混。

题目 6:联合索引的最左前缀原则是什么?

考点

  • 联合索引的排序规则(先按第一列,再按第二列……);
  • 哪些条件能走索引;
  • 边界:范围查询、跳过前导列。

参考答案

  • 联合索引 (a, b, c) 相当于建立了 (a)、(a,b)、(a,b,c) 三套索引(最左前缀)。
  • 必须从第一列开始连续命中,才能用到索引;跳过第一列只查 b 或 c 时,索引失效(或只能部分使用)。
  • 遇到范围查询(>、<、BETWEEN 等)的列之后,后续列无法用于索引定位(但 8.0 下 ICP 可做索引内过滤)。
-- 联合索引 (a,b,c) 的命中规则
CREATE TABLE t_log (
  id BIGINT PRIMARY KEY,
  a INT, b INT, c INT,
  KEY idx_abc (a, b, c)
);

EXPLAIN SELECT * FROM t_log WHERE a = 1 AND b = 2 AND c = 3;  -- ✅ 全用
EXPLAIN SELECT * FROM t_log WHERE a = 1 AND c = 3;            -- ✅ 用 a,c 靠 ICP 过滤(8.0)
EXPLAIN SELECT * FROM t_log WHERE b = 2 AND c = 3;            -- ❌ 跳过 a,全表扫描
EXPLAIN SELECT * FROM t_log WHERE a > 1 AND b = 2;            -- ⚠️ a 用范围,b 大概率用不上定位

追问

  • 问:联合索引列的顺序怎么排?答:等值条件放前面,选择性高的放前面,范围条件尽量放最后,兼顾排序需求。
  • 问:a = 1 AND b > 2 AND c = 3 能用上 c 吗?答:不能用来定位(b 是范围后 c 失序),但 8.0 ICP 可在索引内继续过滤 c,减少回表。

题目 7:一个 SQL 只能用一个索引吗?

考点

  • 单表查询与多个索引的关系;
  • 索引合并(Index Merge)机制。

参考答案

  • 一个 SQL 执行时通常只选一个索引(优化器选代价最小的),但 MySQL 也有索引合并(Index Merge)机制:OR 连接的多个等值条件可以同时用多个索引取交集(Intersection)/并集(Union),再用回表。
  • 出现 Using intersect/union(sort_union) 表示走了索引合并。索引合并往往是"优化器被逼无奈",多数场景不如设计一个联合索引。
-- OR 触发索引合并
CREATE TABLE t_user (id BIGINT PRIMARY KEY, name VARCHAR(50), email VARCHAR(100),
  KEY idx_name (name), KEY idx_email (email));
EXPLAIN SELECT * FROM t_user WHERE name = 'a' OR email = 'b@c.com';
-- type=index_merge, Extra=Using union(idx_name,idx_email)

追问

  • 问:OR 两个索引列,性能一定好吗?答:不一定,合并后还要回表去重,数据量大时不如 UNION ALL 改写或联合索引。
  • 问:什么时候该把 OR 改写成 UNION ALL?答:两个分支都命中索引、结果集都不小、回表量大时,改写往往更稳。

题目 8:索引能提升所有查询性能吗?

考点

  • 索引的代价:写入变慢、占用空间、内存缓存压力;
  • 优化器要权衡的"回表成本"。

参考答案

  • 不能,索引有代价:

1. 写入变慢:每次 INSERT/UPDATE/DELETE 都要同步维护所有索引的 B+ 树;

2. 空间占用:每个索引都是一棵 B+ 树,占磁盘和缓冲池内存;

3. 查询未必变快:如果命中行数占比太高(比如选择性很差的列),优化器会认为回表成本高于全表扫描,放弃索引。

  • 经验法则:单表索引数量控制在5~6 个以内;选择性 < 20% 的列慎建索引。
-- 优化器放弃索引的例子:status 只有 0/1,选择性差
CREATE TABLE t_order (id BIGINT PRIMARY KEY, status TINYINT, KEY idx_status (status));
EXPLAIN SELECT * FROM t_order WHERE status = 1;
-- 若表中大部分行 status=1,优化器会直接 type=ALL 全表扫描

追问

  • 问:怎么判断"命中比例太高"?答:看 rows 预估与表总行数比例,超过 15%~20% 优化器倾向全表扫描;用 ANALYZE TABLE 更新统计信息后再 EXPLAIN。
  • 问:写多读少的表要索引吗?答:只给高频查询建最小必要索引,其余能省则省。

题目 9:哈希索引和 B+ 树索引的区别?

考点

  • 哈希索引的适用场景;
  • InnoDB 的"自适应哈希索引"(AHI)。

参考答案

  • 哈希索引:等值查找 O(1),但不支持范围查询、排序、前缀匹配,也无法用于 ORDER BY / GROUP BY;哈希冲突时退化。
  • MySQL 中:
  • Memory 引擎支持显式 HASH 索引;
  • InnoDB 不开放手工建哈希索引,但它有自适应哈希索引(Adaptive Hash Index, AHI)——当某个值频繁等值命中时,InnoDB 自动在 B+ 树之上加一层哈希加速,对用户透明。
  • B+ 树索引:支持等值、范围、排序、前缀,是 InnoDB 的通用选择。
-- Memory 引擎哈希索引示例(学习用,生产首选 InnoDB)
CREATE TABLE t_cache (
  key_name VARCHAR(64) PRIMARY KEY,
  val      VARCHAR(255)
) ENGINE=MEMORY;   -- 默认即 HASH,等值极快,重启数据丢失

追问

  • 问:Redis 的哈希和 MySQL 的哈希索引一回事吗?答:数据结构思想一样(哈希桶),但 Redis 是内存键值,MySQL 索引要落盘、要事务一致性,实现完全不同。
  • 问:AHI 需要配置吗?答:8.0 默认开启,innodb_adaptive_hash_index=ON,一般不动。

题目 10:全文索引与普通索引的区别?

考点

  • 全文索引的适用场景(LIKE '%x%' 的替代);
  • 倒排索引概念;
  • 8.0 默认分词器 ngram。

参考答案

  • 普通索引适合等值、范围、前缀匹配,LIKE '%关键词%' 这种中间模糊匹配走不了普通索引;
  • 全文索引(FULLTEXT)解决全文检索:底层是倒排索引(词 → 文档列表),适合搜索场景;MySQL 8.0 用 ngram 分词器支持中文。
-- 全文索引示例(MySQL 8.0,ngram 支持中文)
CREATE TABLE t_article (
  id    BIGINT PRIMARY KEY,
  title VARCHAR(200),
  body  TEXT,
  FULLTEXT KEY ft_body (body) WITH PARSER ngram
) ENGINE=InnoDB;

-- 自然语言模式搜索(替代 LIKE '%优化%')
SELECT id, title FROM t_article
WHERE MATCH(body) AGAINST('索引优化' IN NATURAL LANGUAGE MODE);

追问

  • 问:为什么不用 LIKE?答:%关键字% 无法用 B+ 树索引,数据量大时全表扫描,全文索引用倒排表定位词,快得多。
  • 问:全文索引和 ES 的区别?答:MySQL 全文索引适合中小规模站内搜索;大规模、高并发搜索建议 Elasticsearch 等专业检索引擎。

题目 11:索引下推是什么?

考点

  • ICP(Index Condition Pushdown)的定义;
  • 减少回表行数的原理;
  • Extra 中 Using index condition 的识别。

参考答案

  • 索引下推(ICP, Index Condition Pushdown):MySQL 5.6+ 优化,把 WHERE 中能由索引列判断的条件下推到存储引擎层,在回表之前先在索引记录上过滤,减少回表次数。
  • 典型场景:联合索引 (a,b),查询 WHERE a=1 AND b LIKE 'x%'——b 不是前缀条件无法用于定位,但 ICP 可以在索引内先过滤 b,避免把所有 a=1 的行都拉回表。
  • EXPLAIN 中 Extra = Using index condition 即表示 ICP 生效。
-- ICP 生效示例
CREATE TABLE t_user (id BIGINT PRIMARY KEY, name VARCHAR(50), age INT,
  KEY idx_name_age (name, age));
EXPLAIN SELECT * FROM t_user WHERE name = '张' AND age > 20;
-- Extra 出现 Using index condition:age>20 在索引内先过滤,再回表

追问

  • 问:ICP 和覆盖索引谁更强?答:覆盖索引完全不用回表(Using index);ICP 只是减少回表行数,两者可同时出现。
  • 问:ICP 对所有条件都生效吗?答:只对索引内的列生效,索引外的条件不行。

题目 12:为什么建议索引选择性高的列?

考点

  • 选择性的定义:distinct 值 / 总行数;
  • 低选择性对索引性价比的影响。

参考答案

  • 选择性 = COUNT(DISTINCT 列) / COUNT(*),越接近 1 越好;
  • 选择性高意味着每个索引值对应的行数少,走索引后回表行数少,索引才有价值;
  • 低选择性列(性别、状态位)建索引,命中行占比大,优化器常放弃索引,索引变成纯写入负担;
  • 折中手段:用联合索引把低选择性列与高选择性列组合,或对低选择性列加业务条件缩小范围。
-- 计算选择性
SELECT COUNT(DISTINCT username) / COUNT(*) AS selectivity FROM t_user;
-- 接近 1 说明 username 适合建索引
SELECT COUNT(DISTINCT status) / COUNT(*) AS selectivity FROM t_order;
-- 接近 0 说明 status 单独建索引性价比很低

追问

  • 问:性别列什么时候建索引?答:当查询固定是"性别=男 且 城市=北京 且 最近登录"这类组合时,作为联合索引的后置列参与即可,别单独建。
  • 问:前缀索引能提升选择性吗?答:对长字符串可建 (col(10)) 前缀索引减少索引体积,代价是可能引入误判与无法覆盖。

题目 13:NULL 值对索引的影响?

考点

  • NULL 与索引的兼容性;
  • 三个值的逻辑(NULL、TRUE、FALSE)。

参考答案

  • 索引可以包含 NULL(普通索引、唯一索引都允许),但要注意:

1. WHERE col IS NULL 在 8.0 可以走索引(优化器支持);

2. 唯一索引允许多个 NULL;

3. 用 = 匹配 NULL 永远不成立,必须用 IS NULL / IS NOT NULL / ISNULL()。

  • 设计建议:能用 NOT NULL + DEFAULT 表达语义的列尽量 NOT NULL,避免统计和 JOIN 时被 NULL 语义坑到。
-- NULL 查询必须用 IS NULL
SELECT * FROM t_user WHERE deleted_at IS NULL;   -- ✅ 走索引(若建有索引)
SELECT * FROM t_user WHERE deleted_at = NULL;    -- ❌ 永远查不到,NULL 不等于任何值

追问

  • 问:NOT IN 遇到 NULL 会怎样?答:WHERE id NOT IN (1, NULL) 结果为空集(NULL 参与比较结果未知),面试高频陷阱,务必会解释。
  • 问:COUNT(col) 和 COUNT(*) 在 NULL 上区别?答:COUNT(*) 统计所有行,COUNT(col) 忽略 NULL 行。

题目 14:冗余索引与重复索引有什么区别?

考点

  • 重复索引:完全相同的列组合;
  • 冗余索引:能被其他索引覆盖的最左前缀;
  • 索引治理思路。

参考答案

  • 重复索引:同一列建了完全一样的索引(如建表时主键 + 再建一个唯一同列索引);
  • 冗余索引:索引 A 的最左前缀已经包含索引 B 的功能,B 就是冗余的。例如已有 (a,b,c),再建 (a) 就是冗余;但 (a,c) 和 (b) 不算冗余(前导列不同)。
  • 危害:写入维护多、占空间、优化器选择困难。
  • 治理:用 information_schema.STATISTICS 排查同前缀索引,删除冗余;8.0 的 sys.schema_redundant_indexes 视图可直接查。
-- 找出冗余/重复索引(8.0 自带视图)
SELECT * FROM sys.schema_redundant_indexes WHERE table_schema = 'demo';
-- 也可以手写:查同一表所有索引的列前缀
SELECT table_name, index_name, GROUP_CONCAT(column_name ORDER BY seq_in_index) AS cols
FROM information_schema.STATISTICS
WHERE table_schema = 'demo' GROUP BY table_name, index_name;

追问

  • 问:(a,b) 和 (b,a) 算重复吗?答:不算,列顺序不同,各自服务不同的查询模式。
  • 问:删冗余索引要注意什么?答:先确认线上没有查询依赖它(看慢日志/performance_schema),选低峰期删。

题目 15:为什么 B+ 树索引能扛住千万级数据?

考点

  • 树高与磁盘 IO 的关系;
  • 16KB 页与扇出(fanout)计算。

参考答案

  • B+ 树非叶子节点只存键和指针,一页 16KB 能放下大量键值,扇出极大,树高通常只有 3~4 层;
  • 粗略估算:8 字节 BIGINT + 6 字节指针,每页约可存 16KB/14B ≈ 1000+ 个键,第二层页数就能覆盖百万级键,第三层覆盖上亿;
  • 每次查询沿树定位只做 树高次磁盘 IO(约 3~4 次),加上缓存命中,实际大部分 IO 在内存中完成;
  • 千万级数据对 B+ 树来说"不成问题",瓶颈通常在回表和排序,而不是树本身。
-- 查看 InnoDB 页大小(8.0 默认 16K)
SHOW VARIABLES LIKE 'innodb_page_size';
-- 16384 = 16KB

追问

  • 问:树高能不能压到 2 层?答:可以,提高页大小或缩短键长度(如前缀索引)能增加扇出,但页变大会增加单次 IO 成本,需权衡。
  • 问:1000 万行的表建了索引为什么还是慢?答:大概率不是树查找慢,而是回表次数多、扫描行数多、排序或随机 IO——往这三个方向查。

题目 16:MySQL 怎么决定要不要用索引?

考点

  • 优化器与代价模型;
  • 统计信息(ANALYZE TABLE);
  • Explain 中 rows、type、Extra 的判读。

参考答案

  • 优化器基于代价模型(随机 IO 次数、顺序 IO 次数、CPU 成本等)估算各方案成本,选最小的执行计划;
  • 估算依赖统计信息(information_schema.STATISTICS 的 cardinality),统计信息过期会导致选错索引,可用 ANALYZE TABLE 刷新;
  • 判读执行计划三个关键点:

1. type:system > const > eq_ref > ref > range > index > ALL(从好到坏);

2. key:实际用的索引;rows:预估扫描行数(是估算,不是实际值);

3. Extra:Using index(覆盖)、Using index condition(ICP)、Using filesort(额外排序,需警惕)、Using temporary(临时表)。

  • 必要时可用 FORCE INDEX 强制指定,但根治还是设计合理的索引。
-- 刷新统计信息后重新看执行计划
ANALYZE TABLE t_order;
EXPLAIN SELECT order_no FROM t_order WHERE status = 1 ORDER BY created_at DESC LIMIT 10;

追问

  • 问:rows 等于实际扫描行数吗?答:不等于,它是估算值,真实值要看 EXPLAIN ANALYZE(8.0.18+)输出的 actual rows。
  • 问:什么时候该 FORCE INDEX?答:极少用;统计信息长期不准且无法刷新时临时兜底,根本解是修正统计或调整索引。

复习清单

| 题目 | 核心结论 | 易错点 |

|---|---|---|

| 索引本质 | B+ 树有序结构,减扫描 | 别答成"二叉树" |

| 聚簇/非聚簇 | 聚簇存整行,二级索引存主键 | 一张表只能一个聚簇索引 |

| 主键选型 | 自增 BIGINT 最优 | UUID 导致页分裂 |

| 回表/覆盖 | 覆盖索引 Extra=Using index | 与 ICP 的 Using index condition 区分 |

| 最左前缀 | 从第一列连续命中 | 范围列之后失序 |

| 索引代价 | 写慢、占空间、低选择性被弃 | 别答"索引永远更快" |

| NULL | 可入索引,比较用 IS NULL | = NULL 永远不成立 |

| 冗余索引 | 同前缀即冗余 | (a,b) 与 (b,a) 不同 |

练习题

练 1:现有联合索引 (city, age, score),判断以下 SQL 能否走索引、能用到哪几列:WHERE age = 20 AND city = '北京' ORDER BY score。

<details>

<summary>参考答案</summary>

等值条件 city、age 可换序(优化器会重排),都用于定位;score 在索引内,ORDER BY score 无需额外排序(索引顺序天然有序),Extra 大概率 Using index condition,无 filesort。

</details>

练 2:写一条 SQL 用 sys.schema_redundant_indexes 找出 demo 库的冗余索引。

<details>

<summary>参考答案</summary>

SELECT * FROM sys.schema_redundant_indexes WHERE table_schema = 'demo';

</details>

下一篇预告:第 2 章 索引优化与失效场景面试精讲

— 全文完 —回到顶部 ↑
下载推广海报

文章推广海报

《第 1 章 索引基础面试精讲》完整推广海报
DISCUSSION

文章回复

0 条公开回复
未登录回复需要审核后公开
还没有回复,欢迎参与讨论。