B+ 树索引分为两种:
聚簇索引 (Clustered Index / 主键索引)
- 机制: “数据即索引,索引即数据”。
- 叶子节点: 直接存储了完整的数据行 (Data Row)。
- 特点: 每个表只能有1个聚簇索引(通常就是你的主键)。按主键 id 查询时,一次 B+ 树查找就能拿到所有数据,速度最快。
二级索引 (Secondary Index / 非主键索引)
- 机制: 这是一个独立的 B+ 树,例如你在 username 字段上建了索引。
- 叶子节点: 只存储该 username 对应的“主键 ID”。
- 特点:当你查询 WHERE username = ‘Gemini’ 时:
- 先查“username 索引 B+ 树”,找到叶子节点,得到主键 id = 123。
- 再用 id = 123 去查“主键索引 B+ 树”。
- 这个“查两次”的过程,被称为“回表”(Covering Index Lookup)。
为什么不用哈希表 (Hash Table)?
哈希表的优点:在等值查询(=)的场景下,它的平均时间复杂度是 O(1),理论上是最快的。例如 WHERE user_id = 12345。
哈希表的致命缺点:哈希表存储的数据是无序的。它通过 Hash 函数将 Key 映射到桶(Bucket)中,Key 的逻辑顺序(如 1, 2, 3)和它们的存储位置毫无关系。这导致它完全无法支持“范围查询”。 - 如果你执行 WHERE age > 20,哈希表无能为力,它只能退化为全表扫描(O(N)),因为 age=21 和 age=22 的数据存储在完全不相干的位置。 - 结论: 数据库中“范围查询”(>, <, BETWEEN)和“排序”(ORDER BY)的场景极其普遍,一个无法支持范围查询的索引结构是不可接受的。
为什么用 B+ 树 (B+ Tree)?
B+ 树是 B 树的一种变体,它在 B 树的基础上做了两项关键优化,使其成为“为磁盘 I/O 而生”的完美数据结构。
- “矮胖”结构 (低高度 -> 少 I/O):
- 核心痛点: 数据库的数据在磁盘上,而磁盘 I/O 是一个极慢的操作(比内存慢 10 万倍)。
- B+ 树的优化: 它的一个节点(Node)的大小被设计为等于一个“页”(Page,如 16KB)。一个 16KB 的节点可以存放成百上千个索引键。
- 结果: 极高的“扇出”(Fan-out)带来了极低的“树高”。一个存储了几亿条数据的 B+ 树,其高度通常也只有 3 到 4 层。
- 优势: 这意味着数据库最多只需要 3~4 次磁盘 I/O 就能定位到任意一条数据。
- 叶子节点链表 (支持范围查询):
- B+ 树的优化:所有非叶子节点(内部节点)都只存储索引键 (Key),不存储数据 (Value)。这让它们能放下更多 Key,进一步降低树高。所有的数据行 (Value) 都只存储在叶子节点上。
- (关键) 所有叶子节点通过一个双向链表按顺序连接起来。这使得范围查询变得极其高效。
- 步骤说明: 当执行 WHERE age > 20 AND age < 30 时,数据库通过 B+ 树(3~4 次 I/O)找到 age=20 的那个叶子节点。然后沿着叶子节点的链表,向右顺序扫描(这是最高效的磁盘操作),直到 age >= 30 为止。