深入剖析MySQL索引底层:B+树、联合索引与跳跃扫描原理全解

一、索引的本质:有序的数据结构

索引的本质是什么?

索引是帮助MySQL高效获取数据的排好序的数据结构。

为什么需要排序?因为有序的数据结构能极大提升查找效率,就像查字典时的目录一样。


二、索引数据结构演进史

2.1 为什么不使用二叉树?

虽然二叉树查找时间复杂度为O(log n),但存在严重缺陷:

  • 数据有序插入时,会退化为链表(O(n))

  • 每个节点只存储一个键值,导致树高度过高,IO次数增多

2.2 红黑树的局限性

红黑树(平衡二叉树)解决了退化为链表的问题,但依然存在:

  • 每个节点存储数据少,树高度仍然很高

  • 大量数据时,树深度过深,磁盘IO次数过多

2.3 Hash表:快速但不万能

Hash表通过对key做一次hash计算直接定位数据位置,时间复杂度O(1)。
但致命缺陷:

  • 仅支持等值查询(=、IN),不支持范围查询(>、<、between)

  • 存在hash冲突问题

  • 无法利用索引完成排序

2.4 B-Tree:多路平衡查找树

B-Tree的特点:

  • 叶节点具有相同的深度

  • 叶节点指针为空

  • 节点中的数据索引从左到右递增排列

  • 所有索引元素不重复

B-Tree的节点结构:[key|data|key|data|...]

每个节点既存储索引key,也存储对应的数据data。

2.5 B+Tree:MySQL的最终选择

B+Tree是B-Tree的变种,也是MySQL索引的默认数据结构。

B+Tree核心特点:

  1. 非叶子节点不存储data,只存储索引(冗余),可以存放更多索引

  2. 叶子节点包含所有索引字段

  3. 叶子节点用指针连接,形成双向链表,极大提升区间访问性能

B+Tree节点结构:

  • 非叶子节点:[key|pointer|key|pointer|...]

  • 叶子节点:[key|data|next_pointer]

为什么选择B+Tree?

  • 树高度更低:每个节点可存储更多key,减少磁盘IO次数

  • 查询更稳定:所有查询都要走到叶子节点,时间复杂度稳定为O(log n)

  • 范围查询高效:叶子节点双向链表结构,支持快速范围查询

  • 适合磁盘存储:节点大小通常设置为页大小(16KB),减少磁盘IO


三、存储引擎的索引实现差异

3.1 MyISAM:非聚集索引

MyISAM存储引擎中,索引文件和数据文件是分离的:

  • .MYI文件存储索引

  • .MYD文件存储数据

索引结构中的data存储的是数据记录的地址指针

3.2 InnoDB:聚集索引

InnoDB存储引擎采用聚集索引

  • 表数据文件本身就是按B+Tree组织的索引结构

  • 叶子节点包含了完整的数据记录

  • 主键索引的叶子节点存储整行数据

为什么InnoDB表必须建主键?

  • InnoDB的数据文件本身就是主键索引文件

  • 如果没有显式定义主键,MySQL会自动选择:

    1. 第一个唯一非空索引

    2. 自动生成隐藏的row_id作为主键

为什么推荐使用整型自增主键?

  1. 节省空间:整型比字符串占用的存储空间小

  2. 查询高效:整型比较比字符串比较快

  3. 自增避免页分裂:插入时顺序写入,减少B+Tree结构调整


四、联合索引与最左前缀原理

4.1 联合索引存储结构

创建联合索引:

复制代码
CREATE TABLE `employees` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(24) NOT NULL DEFAULT '' COMMENT '姓名',
  `age` int(11) NOT NULL DEFAULT '0' COMMENT '年龄',
  `position` varchar(20) NOT NULL DEFAULT '' COMMENT '职位',
  `hire_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '入职时间',
  PRIMARY KEY (`id`),
  KEY `idx_name_age_position` (`name`, `age`, `position`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='员工记录表';

联合索引的B+Tree结构:

  • 按照(name, age, position)的顺序构建

  • 先按name排序,name相同按age排序,都相同按position排序

  • 叶子节点存储这三个字段的值和主键id

4.2 最左前缀匹配原则

在MySQL 8.0之前,必须严格遵守最左前缀原则:

✅ 能使用索引的查询:

复制代码
-- 使用name(最左列)
EXPLAIN SELECT * FROM employees WHERE name = 'Bill';

-- 使用name和age
EXPLAIN SELECT * FROM employees WHERE name = 'Bill' AND age = 31;

-- 使用name、age、position
EXPLAIN SELECT * FROM employees WHERE name = 'Bill' AND age = 31 AND position = 'dev';

❌ 不能使用索引的查询:

复制代码
-- 缺少最左列name
EXPLAIN SELECT * FROM employees WHERE age = 30 AND position = 'dev';

-- 只有position
EXPLAIN SELECT * FROM employees WHERE position = 'manager';

五、MySQL 8.0的索引跳跃扫描(Index Skip Scan)

5.1 什么是索引跳跃扫描?

MySQL 8.0.13引入的优化特性,允许在某些情况下跳过联合索引的最左列

官方示例:

复制代码
CREATE TABLE t1 (f1 INT NOT NULL, f2 INT NOT NULL, PRIMARY KEY(f1, f2));
-- 插入数据...

EXPLAIN SELECT f1, f2 FROM t1 WHERE f2 > 40;

执行计划显示:

复制代码
Extra: Using where; Using index for skip scan

5.2 索引跳跃扫描原理

MySQL优化器将查询重写为多个查询的UNION:

复制代码
-- 原查询
SELECT f1, f2 FROM t1 WHERE f2 > 40;

-- 实际执行的查询(假设f1有1和2两个值)
SELECT f1, f2 FROM t1 WHERE f1 = 1 AND f2 > 40
UNION
SELECT f1, f2 FROM t1 WHERE f1 = 2 AND f2 > 40;

执行步骤:

  1. 获取联合索引第一列(f1)的所有不同值

  2. 为每个值构造查询:f1 = value AND f2 > 40

  3. 执行每个查询并合并结果

5.3 使用场景与限制

适用场景:

  • 联合索引第一列的不同值较少(低区分度)

  • 查询条件中不包含第一列

限制条件:

  • 只能用于单表查询,不能多表JOIN

  • 不能使用GROUP BY或DISTINCT

  • 查询字段必须都在索引中

  • 第一列必须出现在索引中

性能考虑:

虽然跳跃扫描提供了便利,但不能依赖此优化。建立索引时仍应:

  1. 将区分度高、查询频繁的字段放在最左边

  2. 尽量避免在查询时省略最左列


六、为什么非主键索引叶子节点存储主键值?

  1. 保持数据一致性:当数据行更新时,只需更新主键索引,非主键索引无需改动

  2. 节省存储空间:存储主键值比存储整行数据小得多

  3. 避免数据冗余:减少存储空间占用和写入时的开销


七、Java开发中的索引优化实践

7.1 索引设计原则

  • 为频繁查询的where条件字段创建索引

  • 联合索引注意字段顺序:区分度高的在前

  • 避免在索引列上使用函数或表达式

  • 控制索引数量,避免过度索引

7.2 监控索引使用情况

复制代码
-- 查看索引使用情况
SELECT * FROM sys.schema_unused_indexes;

-- 查看冗余索引
SELECT * FROM sys.schema_redundant_indexes;

7.3 常用优化技巧

  1. 使用覆盖索引,避免回表

  2. 利用索引下推(ICP)减少回表次数

  3. 注意索引失效场景(类型转换、函数计算等)


八、总结

MySQL索引的核心是B+Tree数据结构,它平衡了查询效率和存储成本。InnoDB的聚集索引设计让主键查询极为高效,而联合索引的最左前缀原则是SQL优化的关键点。

MySQL 8.0的索引跳跃扫描虽然打破了最左前缀的绝对限制,但只是特定场景下的优化手段,不能替代良好的索引设计。

相关推荐
oMcLin8 小时前
如何在 AlmaLinux 9 上配置并优化 Redis 集群,支持高并发的实时数据缓存与快速查询?
数据库·redis·缓存
oMcLin8 小时前
如何在Debian 11上通过配置MySQL 8.0的分布式架构,提升跨区域数据同步的效率与延迟?
分布式·mysql·debian
洛阳纸贵8 小时前
Redis
数据库·redis·缓存
计算机学姐9 小时前
基于SpringBoot的校园资源共享系统【个性化推荐算法+数据可视化统计】
java·vue.js·spring boot·后端·mysql·spring·信息可视化
l1t9 小时前
DeepSeek辅助编写的利用位掩码填充唯一候选数方法求解数独SQL
数据库·sql·算法·postgresql
墨月白9 小时前
[QT] QT中的折线图和散点图
数据库·qt
龙潜月七9 小时前
做一个背单词的脚本
数据库·windows·c#·aigc·程序那些事
我科绝伦(Huanhuan Zhou)9 小时前
DM数据库物理存储结构深度解析与理论实践
数据库·oracle
霖霖总总9 小时前
[小技巧23]全面理解 MySQL 的 WAL 机制:原理、影响与可观测性
数据库·mysql