← 返回文章

索引

发布于 2026/10/17

1. 完整回答

索引是数据库中用于加快数据检索的数据结构,核心作用是减少查询时需要扫描的数据量,提高查询效率。

以 MySQL 的 InnoDB 存储引擎为例,索引主要采用 B+ 树。B+ 树是一种多路平衡搜索树,数据按照索引键有序组织,查询时从根节点逐层定位到叶子节点,可以高效支持等值查询、范围查询和排序。

InnoDB 的索引主要分为聚簇索引和二级索引。聚簇索引通常由主键构建,叶子节点保存完整的行数据;二级索引的叶子节点保存索引列和对应的主键值。如果通过二级索引查询的字段没有被索引覆盖,就需要根据主键再次查询聚簇索引,这个过程叫作回表。

实际开发中,索引通常建立在查询条件、关联条件、排序字段上。对于多条件查询,可以使用联合索引,并根据最左前缀原则设计字段顺序。通过覆盖索引,还可以减少回表操作。

不过,索引会占用额外存储空间,而且插入、更新、删除时都需要维护索引。索引也不保证一定被使用,优化器会根据数据量、选择性和访问成本决定执行方式。

因此,索引设计需要结合实际业务 SQL,通过 EXPLAIN 分析执行计划,在查询性能、存储空间和写入成本之间取得平衡。

2. 核心原理与关键概念

2.1 核心原理

(1)B+ 树的存储结构

InnoDB 的 B+ 树索引具有以下特点:

  • 非叶子节点:主要保存索引键和子节点指针,负责导航。

  • 叶子节点:保存实际的索引记录,并按照索引键有序排列。

  • 叶子节点之间:通过链表连接,便于范围扫描。

  • 多路平衡结构:每个节点可以包含多个索引键,树的高度通常较低,减少磁盘 I/O。

(2)索引查询流程

以通过二级索引查询用户信息为例:

SQL 查询 → 优化器选择索引 → 从 B+ 树根节点逐层定位 → 找到叶子节点中的索引记录 → 获取主键 → 必要时回表 → 返回结果。

其中,是否回表取决于查询所需的数据能否直接从索引中获得。

如果查询字段已经被二级索引覆盖,就可以直接返回结果,减少访问聚簇索引的开销。

(3)联合索引的匹配机制

假设存在联合索引 (a, b, c),索引先按 a 排序,a 相同时按 b 排序,前两列都相同时再按 c 排序。

因此:

  • a = 1:可以利用索引定位。

  • a = 1 AND b = 2:可以继续缩小查找范围。

  • a = 1 AND b = 2 AND c > 3:可以利用连续等值条件和范围条件定位。

  • b = 2 AND c = 3:通常无法直接利用该联合索引进行高效的连续范围定位。

当某列使用范围条件后,后续列通常难以继续缩小连续扫描范围,但仍可能参与索引内过滤。

2.2 关键概念

概念 含义
聚簇索引 叶子节点保存完整行记录,InnoDB 通常使用主键构建
二级索引 叶子节点保存索引列和主键值
联合索引 对多个字段按照指定顺序建立的索引
最左前缀原则 联合索引通常从最左侧字段开始匹配,才能高效缩小查找范围
回表 通过二级索引获取主键后,再查询聚簇索引获取所需字段
覆盖索引 一个索引包含查询需要的全部字段,可以避免额外回表
索引下推(ICP) 在存储引擎层利用索引字段提前过滤记录,减少不必要的回表
选择性 查询条件能够过滤掉的数据比例,影响索引的使用价值

3. 高频面试追问

3.1 为什么 MySQL 使用 B+ 树作为索引结构?

B+ 树能够同时兼顾等值查询、范围查询和磁盘访问效率,非常适合数据库索引。

相比普通二叉搜索树,B+ 树是多路平衡树,一个节点能够保存多个键值,通常可以用较少的层级定位数据,从而减少磁盘 I/O。

相比传统 B 树,B+ 树主要在叶子节点保存实际记录,非叶子节点可以容纳更多用于导航的键,具有较大的分支数。

相比哈希索引,B+ 树中的数据有序,除了等值查询,还可以支持范围查询以及部分排序操作。

例如查询某个月的订单,B+ 树可以先定位到起始日期,再沿着叶子节点扫描后续记录。

所以,B+ 树的主要优势是树高较低、查询稳定,并且支持有序访问。

3.2 聚簇索引和二级索引有什么区别?为什么会产生回表?

两者最核心的区别是叶子节点保存的内容不同。

InnoDB 的聚簇索引叶子节点保存完整行数据,因此根据主键查询,可以直接定位并读取记录。

二级索引叶子节点保存索引列和主键值。如果查询还需要其他字段,就要根据主键再次访问聚簇索引,这就是回表。

例如用户表包含 id、name、age,在 name 上建立二级索引。

执行 SELECT * FROM user WHERE name = 'Tom' 时,会先通过 name 索引找到主键,再通过主键获取完整记录。

回表可能带来额外的随机 I/O,匹配数据越多,回表成本可能越高。因此可以通过合理设计覆盖索引、减少返回字段等方式优化。

3.3 什么是覆盖索引?它和索引下推有什么区别?

覆盖索引可以避免回表,索引下推可以减少不必要的回表次数。

覆盖索引指查询需要的字段都能从同一个索引中获取。

例如建立 (name, age) 联合索引,执行:

SELECT age FROM user WHERE name = 'Tom'

查询条件和返回字段都包含在索引中,可以直接读取二级索引记录。

索引下推则发生在查询仍然需要读取完整行数据的场景中。

例如:

SELECT * FROM user WHERE name LIKE 'T%' AND age = 20

在 (name, age) 索引上,数据库可以先定位符合 name 前缀的索引记录,再利用索引中的 age 过滤数据,最后对满足条件的记录回表。

因此,覆盖索引减少的是额外的数据读取,索引下推减少的是无效的数据读取。

3.4 联合索引为什么要遵循最左前缀原则?范围查询会导致索引失效吗?

最左前缀原则源于联合索引的多字段有序存储方式。范围查询通常只会限制后续字段继续缩小索引扫描范围的能力。

例如联合索引 (a, b, c),数据按照 a → b → c 的顺序排列。

查询 a = 1 AND b = 2 AND c = 3,可以通过三个字段共同定位。

查询 a = 1 AND b > 2 AND c = 3,可以利用 a 和 b 定位范围,但由于这个范围内的 c 并非全局有序,通常无法继续利用 c 缩小连续扫描区间。

不过,c = 3 仍可能通过索引下推参与过滤。

另外,SQL 中 WHERE 条件的书写顺序通常不影响最左前缀匹配,优化器会分析和调整条件。

继续追问:缺少最左侧字段,联合索引就完全不能使用吗?

也不能绝对这么判断。MySQL 在满足特定条件时可以使用跳跃扫描(Skip Scan),也可能进行全索引扫描。不过,这些方式有使用条件和成本限制,通常优先设计符合主要查询模式的索引。

3.5 哪些情况容易导致索引无法有效使用?

常见原因包括无法利用索引有序性定位、联合索引匹配受限,以及优化器认为索引访问成本过高。

比较典型的情况有:

  1. 对索引列使用函数或计算:例如 YEAR(create_time) = 2026,普通 create_time 索引通常无法直接进行范围定位,可以改写成时间区间查询。

  2. LIKE 以通配符开头:例如 LIKE '%abc',通常无法利用普通 B+ 树索引高效定位前缀。

  3. 隐式类型转换:例如字符串字段与数字直接比较,可能导致无法利用索引进行查找。

  4. 联合索引缺少前导列:通常无法进行高效的连续范围定位。

  5. 查询匹配数据过多:优化器可能选择全表扫描,避免大量回表。

需要注意,OR、IN、IS NULL、!= 等条件并不必然导致索引失效,是否使用索引取决于具体条件、索引结构和执行计划。

另外,MySQL 8.0.13 及之后支持函数索引,特定表达式也可以建立索引。

3.6 联合索引的字段顺序应该怎么设计?

联合索引要根据实际查询模式设计,综合考虑等值条件、范围条件、排序需求和字段选择性。

通常会先考虑经常使用的等值查询字段,再考虑范围查询和排序字段,同时尽量控制索引宽度。

例如订单表经常执行以下查询:

WHERE user_id = ? AND status = ? ORDER BY create_time DESC

可以考虑建立 (user_id, status, create_time) 联合索引。

这样能够先定位指定用户和状态的订单,再按创建时间顺序读取数据,在适当的执行计划下减少额外排序。

但如果大量查询只根据 create_time 筛选,这个联合索引就未必合适。

所以,索引字段顺序需要结合真实 SQL 和数据分布验证。

继续追问:区分度最高的字段一定要放在最前面吗?

不一定。区分度是重要因素,但还要考虑查询条件的组合、排序需求和索引复用情况。尤其是多个等值条件同时出现时,不能仅根据区分度决定字段顺序。

3.7 为什么建立了索引,MySQL 仍然可能选择全表扫描?

因为 MySQL 优化器会比较不同执行方案的成本,使用索引不一定总是更快。

例如某张订单表有一百万条数据,其中九十万条状态都是已完成。

如果通过状态索引查询全部已完成订单,并且需要返回大量非索引字段,就可能产生大量回表操作。

这种情况下,顺序扫描整张表可能比通过二级索引反复回表更高效。

优化器会根据索引统计信息、估算扫描行数、查询条件和访问成本选择执行计划。

因此,遇到没有使用索引的情况,需要先检查选择性、返回字段、扫描行数和实际耗时,再决定是否调整索引。

3.8 如何通过 EXPLAIN 判断索引是否使用合理?

主要看实际选择的索引、访问方式、预计扫描行数,以及是否发生额外排序和回表。

常用字段包括:

字段 主要作用
type 表访问方式,常见有 const、ref、range、index、ALL
possible_keys 优化器可能考虑的索引
key 实际选择的索引
key_len 使用的索引键长度,有助于分析联合索引匹配情况
rows 预计需要检查的行数
Extra 展示覆盖索引、索引下推、额外排序等信息

其中:

  • Using index:通常表示使用覆盖索引读取所需字段。

  • Using index condition:表示使用了索引下推。

  • Using filesort:表示需要额外排序。

  • type = ALL:通常表示全表扫描。

  • type = index:表示扫描索引树,不代表一定高效。

EXPLAIN 提供的是执行计划及相关估算。如果需要验证实际扫描行数和耗时,可以使用 EXPLAIN ANALYZE。

分析索引效果时,不能只看有没有使用索引,更要看扫描量和实际执行成本。

3.9 为什么索引不能建立得越多越好?主键应该如何选择?

索引能够提升部分查询性能,但会增加存储和数据写入的成本。

每增加一个索引,都需要额外保存索引记录。插入、删除以及修改索引字段时,也需要维护相应的索引结构,可能涉及页面分裂、合并等操作。

对于 InnoDB,主键还会保存在二级索引记录中,因此主键越长,二级索引通常也越大。

所以,主键通常倾向于选择短小、唯一、稳定的字段。

在适合的业务中,可以使用自增整数主键。递增写入通常具有较好的局部性,有利于减少随机插入造成的页面分裂。

但分布式系统中还需要考虑主键生成方式、全局唯一性和写入并发等问题。

索引设计的核心是保留真正有价值的索引,减少冗余索引,同时兼顾读写性能。

3.10 实际项目中,如何优化一条因为索引问题导致的慢 SQL?

通常按照定位慢 SQL、分析执行计划、调整索引或查询方式、验证效果的顺序处理。

例如订单表数据量较大,用户经常按状态查询订单,并按照创建时间倒序分页。

首先,通过慢查询日志定位 SQL,再使用 EXPLAIN 检查是否存在全表扫描、大量回表或者额外排序。

如果原先只有 user_id 单列索引,可以结合查询条件考虑 (user_id, status, create_time DESC) 联合索引,使过滤和排序尽量利用同一个索引。

接着检查返回字段。如果只需要少量字段,可以评估覆盖索引,但需要权衡索引宽度。

如果使用深度分页,LIMIT offset, size 可能需要跳过大量记录,可以在业务允许的情况下改为基于上一页末尾排序键的游标分页。

最后,通过 EXPLAIN ANALYZE 和实际压测比较扫描行数、查询耗时及写入开销,确认优化是否有效。

4. 易混淆概念对比

对比概念 核心区别
聚簇索引 vs 二级索引 聚簇索引叶子节点保存完整行数据;二级索引保存索引列和主键,读取其他字段通常需要回表
主键索引 vs 唯一索引 主键要求唯一且非空,一张表只能有一个;唯一索引用于约束键值唯一,可以有多个,在 MySQL 中可空唯一索引通常允许多个 NULL
单列索引 vs 联合索引 单列索引针对一个字段;联合索引按多个字段组合排序,可以优化多条件查询及部分排序、分组操作
覆盖索引 vs 索引下推 覆盖索引让查询直接从索引中获取全部所需字段;索引下推在回表前利用索引字段过滤记录
B+ 树索引 vs 哈希索引 B+ 树具有有序性,支持等值、范围和部分排序操作;哈希索引主要适用于等值查找,不支持直接利用哈希顺序进行范围扫描

另外,索引类型与索引结构属于不同的分类维度。主键索引、唯一索引、普通索引主要描述约束或用途;B+ 树、哈希索引主要描述底层数据结构。

5. 总结

索引是数据库用于加快数据检索的数据结构,核心作用是减少扫描行数和数据访问成本。InnoDB 主要使用 B+ 树组织索引,利用多路平衡结构和有序叶子节点支持等值查询、范围查询及部分排序操作。聚簇索引保存完整行数据,二级索引保存索引列和主键值,因此查询可能涉及回表。实际开发中,可以通过联合索引、覆盖索引和索引下推提升查询效率,同时需要遵循索引的有序匹配规律。索引会带来额外的存储和写入成本,而且优化器不一定选择索引,所以最终需要结合业务 SQL、执行计划和实际耗时判断索引是否合理。