零基础学习SQL(十一):SQL 索引结构|从 B+Tree 到 Hash,面试常问的 “为啥选 B+Tree” 有答案了

MySQL 索引完全指南

一、索引的本质

索引是 MySQL 中帮助高效获取数据的有序数据结构

数据库在原始数据外,会维护一套满足特定查找算法的数据结构(如 B+Tree),该结构通过指针关联原始数据,可基于算法快速定位数据,从而避免全表扫描,提升查询效率。

二、索引的优缺点

索引是 "双刃剑",需在查询效率与存储 / 更新成本间平衡:

维度 具体说明
优点 1. 降低 IO 成本:减少数据检索时的磁盘读取次数,快速定位数据;2. 降低 CPU 成本:通过索引列的有序性,避免对原始数据排序,直接利用索引排序结果。
缺点 1. 占用存储空间:索引列需单独存储,一张表的索引越多,占用磁盘空间越大;2. 降低更新速度:INSERT/UPDATE/DELETE 时,需同步维护索引结构(如 B+Tree 调整),额外增加操作耗时。

三、MySQL 支持的索引结构

不同索引结构适用于不同场景,主流存储引擎(InnoDB/MyISAM/Memory)对结构的支持存在差异。

1. 常见索引结构对比

索引结构 核心原理 适用场景 优缺点总结
B+Tree 索引 多路平衡查找树,叶子节点存储数据(或主键),且叶子节点形成有序链表 绝大多数查询场景(单值、范围、排序) ✅ 支持范围查询 / 排序;✅ 层级少、效率高;❌ 需维护树结构
Hash 索引 基于哈希算法,将键值映射到哈希表槽位,冲突时用链表解决 仅精确匹配查询(=、IN) ✅ 单次检索效率极高;❌ 不支持范围查询 / 排序
R-Tree 索引 空间索引,专为地理空间数据(如经纬度)设计 地理空间数据查询(如 "附近的地点") ✅ 支持空间范围查询;❌ 仅 MyISAM 支持,使用极少
Full-Text 索引 基于倒排索引,将文本拆分为关键词,快速匹配包含关键词的文档 文本内容检索(如文章、评论) ✅ 支持模糊文本查询;❌ 仅特定引擎 / 版本支持(InnoDB 5.6+)

2. 各存储引擎对索引结构的支持

索引结构 InnoDB MyISAM Memory
B+Tree 索引 支持(默认) 支持 支持
Hash 索引 不支持(但有自适应 Hash,自动构建) 不支持 支持(默认)
R-Tree 索引 不支持 支持 不支持
Full-Text 索引 5.6 版本后支持 支持 不支持

四、核心索引结构深度解析(B-Tree vs B+Tree vs Hash)

1. 从二叉树到 B-Tree:解决 "层级过深" 问题

  • 二叉树:每个节点最多 2 个子节点,数据量增大时会退化为 "链表"(如有序数据插入),层级极深,IO 次数暴增。
  • 红黑树:通过 "红黑规则" 保持树的平衡,减少层级,但数据量超千万时,层级仍会达到 20+,检索效率不足。
  • B-Tree(多路平衡查找树)

允许每个节点存储多个键值和指针(如 5 阶 B-Tree 可存 4 个键值、5 个指针),大幅减少树的高度。

特点:非叶子节点和叶子节点均存储数据,所有叶子节点在同一层级。

2. B+Tree:MySQL 索引的 "最终选择"

B+Tree 是 B-Tree 的优化版,也是 InnoDB/MyISAM 的默认索引结构,MySQL 对其进一步优化(增加相邻叶子节点的链表指针)

(1)B+Tree 与 B-Tree 的核心区别
对比项 B-Tree B+Tree(MySQL 优化版)
数据存储位置 非叶子节点、叶子节点均存数据 仅叶子节点存数据,非叶子节点只存键值 + 指针
叶子节点关联 无关联 叶子节点形成双向链表(MySQL 优化)
范围查询效率 需回溯节点,效率低 直接遍历叶子节点链表,效率高
(2)MySQL 优化后的 B+Tree 优势
  • 层级更少:非叶子节点不存数据,单个节点可存更多键值,树高通常仅 2-3 层(见 "思考题 2" 计算),IO 次数极少。
  • 范围查询快:叶子节点双向链表,可快速遍历 "某区间数据"(如 age between 20 and 30)。
  • 排序友好:叶子节点有序,可直接利用索引完成排序(如 order by id)。

3. Hash 索引:仅适用于 "精确匹配"

  • 原理:通过哈希函数将键值(如 id=10)换算为哈希值,映射到哈希表的某个槽位,槽位冲突时用链表存储相同哈希值的键值。
  • 局限性
    • 不支持范围查询(如 id>10):哈希值无序,无法判断区间关系;
    • 不支持排序(如 order by id):哈希值与键值无顺序关联;
    • 冲突时效率下降:链表过长会导致检索时间从 "O (1)" 变为 "O (n)"。

五、索引的分类

1. 按 "功能用途" 分类

分类 含义 特点 关键字 / 创建方式
主键索引 针对表的主键创建的索引 默认自动创建,一张表仅 1 个 PRIMARY(如 id int PRIMARY KEY)
唯一索引 确保索引列的值唯一(允许 NULL,但 NULL 仅出现一次) 一张表可多个 UNIQUE(如 name varchar(20) UNIQUE)
常规索引 无特殊约束,仅用于快速定位数据 一张表可多个 无关键字(如 INDEX idx_age (age))
全文索引 对文本内容(如 text 类型)建立倒排索引,支持关键词匹配 一张表可多个,仅支持特定引擎 / 字段 FULLTEXT(如 FULLTEXT idx_content (content))

2. InnoDB 特有的 "存储形式" 分类(核心!)

InnoDB 基于 "聚集索引" 组织数据,这是理解 InnoDB 索引的关键。

分类 含义 特点 叶子节点存储内容
聚集索引 数据与索引 "聚在一起",索引结构即数据存储结构 必须有且仅 1 个,决定数据物理存储顺序 完整的行数据
二级索引 数据与索引分离,索引仅关联主键,需通过主键回查数据("回表") 可多个,不影响数据物理顺序 对应的主键值
聚集索引的选取规则(优先级从高到低):
  1. 若表有主键,则主键索引即为聚集索引;
  1. 若无主键,选择第一个非空的唯一索引作为聚集索引;
  1. 若既无主键也无合适唯一索引,InnoDB 自动生成隐藏的 rowid 作为聚集索引。

六、典型问题与思考题解析

1. 面试题:为什么 InnoDB 选择 B+Tree 作为索引结构?

答:核心原因是 B+Tree 平衡了 "查询效率、范围支持、排序需求",具体对比如下:

  • 对比二叉树 / 红黑树:B+Tree 层级更少(通常 2-3 层),减少磁盘 IO 次数,适合大数据量;
  • 对比B-Tree:B+Tree 仅叶子节点存数据,非叶子节点存更多键值,层级更浅;且叶子节点链表支持高效范围查询;
  • 对比Hash 索引:B+Tree 支持范围查询(如 between)和排序(如 order by),而 Hash 索引不支持。

2. 思考题 1:两条 SQL 执行效率对比

sql 复制代码
select * from user where id = 10;  -- ①
select * from user where name = 'Arm';  -- ②(name 有二级索引)

答:① 执行效率更高,原因如下:

  • id 是主键,对应聚集索引,叶子节点直接存储行数据,仅需 1 次 B+Tree 检索即可获取完整数据;
  • name 是二级索引,叶子节点仅存储主键 id,需先通过二级索引找到 id,再通过聚集索引查行数据(即 "回表"),多 1 次检索步骤。

3. 思考题 2:InnoDB 主键索引的 B+Tree 高度为多少?

答:基于 InnoDB 页大小(默认 16KB)计算,假设条件:

  • 行数据大小:1KB / 行(则 1 页可存 16 行);
  • 主键类型:bigint(8 字节);
  • B+Tree 节点指针:6 字节。
计算过程:
  • 单个非叶子节点能存多少个键值(n)

非叶子节点存储 "键值(8 字节)+ 指针(6 字节)",且最后一个指针额外占用 6 字节,公式为:

n*8 + (n+1)6 ≤ 161024(16KB=16384 字节)

解得 n≈1170(即单个非叶子节点可存 1170 个键值,1171 个指针)。

  • 不同高度的存储容量
    • 高度 = 1:仅 1 个叶子节点,存储 16 行数据;
    • 高度 = 2:1 个非叶子节点(1171 个指针)+ 1171 个叶子节点,总容量 = 1171*16≈1.8 万行;
    • 高度 = 3:1 个顶层非叶子节点 + 1171 个中层非叶子节点 + 11711171 个叶子节点,总容量 = 11711171*16≈2200 万行;
    • 高度 = 4:总容量≈2200 万 * 1171≈260 亿行(远超绝大多数业务场景)。

结论:InnoDB 主键索引的 B+Tree 高度通常为 2-3 层,即使千万级数据,也仅需 2-3 次磁盘 IO 即可定位数据。

相关推荐
代码的余温2 小时前
Linux内核调优实战指南
linux·服务器·数据库
almighty272 小时前
C# DataGridView表头自定义设置全攻略
数据库·c#·winform·datagridview·自定义表头
SamDeepThinking3 小时前
用设计模式重构核心业务代码的一次实战
java·后端·设计模式
ljh5746491193 小时前
mysql 必须在逗号分隔字符串和JSON字段之间二选一,怎么选
数据库·mysql·json
论迹3 小时前
【Redis】-- 持久化
数据库·redis·缓存
getdu3 小时前
Redis面试相关
数据库·redis·面试
TDengine (老段)3 小时前
TDengine 选择函数 TOP() 用户手册
大数据·数据库·物联网·时序数据库·iot·tdengine·涛思数据
m0_694845573 小时前
教你使用服务器如何搭建数据库
linux·运维·服务器·数据库·云计算