Lecture#08 Tree Indexes2
review:
上节课我们说对于非唯一性索引(有重复的key):
- Duplicate Keys:使用相同的叶结点布局,但存储 Duplicate Keys 多次。(更常用)
- Value Lists:每个键 key 存储一次,并维护一个唯一的 value 的链表。
但在B+树中,实际应如何维护这些重复的索引(key)呢?有两种方法:
1️⃣ 通过在 key 后面追加对应 tuple 的 record id 让重复的 key 变得各不相同。

- ❗️ 此时数据库系统实际所保存的是 key 和 record id 这样的属性组合。
- 这还能让B+树正常工作,因为B+树支持通过使用 key 的部分内容进行检索,因此可以直接通过 key 而不是 key+record id 进行检索(前缀搜索)。若在 hash 表中做这样的改动,需要提供 key+record id 才能检索。
2️⃣ 将重复的 key 保存在 overflow 的叶子结点上。

- 这种方式违反了 B+ Tree 设计规则。这里并不是将叶子结点进行水平扩展去容纳新的 entry,而是将叶子结点进行垂直拓展,在给定的叶子结点下面添加 overflow page,将所有重复的 key 添加到 overflow page 中,即类似 Chained Hashing 的思路。
- 更难维护和修改。
大部分人会选择实现第 1 种方式,因为这种方式不会对数据结构进行大改 ,不管是唯一索引还是非唯一索引,所有东西都和之前的一样。缺点就是要将 record ID 作为 key 的额外元素保存起来,而这就会增加索引的 size。
1 Additional Index Usage
隐式索引
:大多数 DBMS 将自动创建索引来强制执行完整性约束(Eg: 主键、唯一性约束),除了参照性约束(外键)。
- 当声明一个完整性约束时,DBMS会自动创建一个索引。否则唯一能执行该完整性约束的方式就是循序扫描。🌰 若要保证主键的唯一性,若不使用索引,插入数据时必须扫描每个 tuple 来保证所有 tuple 的 key 都不同。
- 所有DBMS都会自动为主键做唯一性约束,因此会自动创建索引。但对参照完整性约束(外键),则不会自动创建索引。

部分索引
:在整个表的一个子集上创建索引。这潜在地减少了规模和维护它的开销。
- 这种方式非常常见 🌰 可以通过不同索引将不同日期范围的数据分开,在每个月份上建立索引,这样可以以想要的顺序在该月份中进行快速查找。
- 使用一个部分索引可以避免一堆不需要的数据去污染 Buffer Pool,同时树的高度也会变得更低,这样就有利于更快的查找。

覆盖索引
:若处理查询所需的所有属性都能在索引中获取,则 DBMS 无需再检索 tuple。DBMS 可直接根据索引中可用的数据完成整个查询。
- 这减少了对 Buffer Pool 资源的争用。
- 无需将一个索引声明为覆盖索引,DBMS 会自动帮你做这件事。
- 👆这个查询就是个覆盖索引,因为 idx_foo 索引有a、b属性,查询只想获得b属性,则可直接从索引中获取,而无需通过索引得到 record id 再去 Buffer Pool 中获取对应的 tuple 再得到 b(可能多一次磁盘I/O)。
索引包含列
:在索引中嵌入额外的列以支持仅索引的查询。额外的属性只保存在 leaf node,不会保存在 inner node,inner node 仍只有 search key 需要的属性。
- 因为额外的属性不在 inner node 中,因此不会让索引整体的 size 变得很大。

函数/表达式索引
:将函数或表达式的输出存储为 key,而不是原始值。即通过 key 衍生出来的某些值而不是通过 key 自身的值来进行查找。
- 识别哪些查询可以使用该索引是 DBMS 的工作。

所有DBMS支持隐式索引,大部分支持部分索引,一部分支持覆盖索引,支持索引包含列的更少。
创建索引后,DBMS可以自己从不同的各种索引中找到最好的访问方法并使用。
创建索引时,DBMS默认使用 B+tree,除非强制指定索引类型,比如 Hash Table。
2 Radix Tree
B+树存在的问题:
- B+树中 key 会被多次复制,要判断某个key是否存在于表中,必须遍历到 leaf node。因为 inner node 可能会保存那些不再存在于 tree 中的 key 的拷贝,它们要用这些 key 当作路标。
- 如果 Buffer Pool 中并没有缓存要找的 leaf node 所在的 page,则会出现 page miss,那么在向下遍历B+树时,就会得去磁盘上对每个结点进行查找。
因此若能不遍历到底部的 leaf node,在树的顶部就能知道 key 是否存在于表中的话,就很nice。
——Trie Index
2.1 Trie Tree
Trie (Retrieval Tree)
:又称 Digital Search Tree, Rrefix Tree。是一种树形数据结构,比B+树更古老。
- 思路:将所有的 key 分解为 digit(key 中某些原子子集,如一个 byte or bit 之类的),将分解后的 digit 存于 tree 的不同层,若某层的 digit 重复只需保存一次。
- tree 不同的向下遍历 digit 的路径对应一个 key。

Trie Tree 的特点:
- Trie Tree 的形状取决于 key 的分布以及长度,它是一个确定的数据结构,不管插入 key 的顺序如何,最终都会得到相同的物理数据结构(即结点布局)🆚 B+ Tree 的高度取决于 key 的数目,且结点布局与 key 的插入顺序有关,这取决于 B+Tree 如何进行拆分与合并
- Trie Tree 不用进行重新平衡(垂直层面可以进行 rebalance,水平层面不需要)🆚 B+Tree 需要重新平衡
- Tire Tree 中操作复杂度都为 ,K 为 key 的长度 🆚 B+Tree 中操作复杂度都为
- Trie Tree 的点查询速度快于 B+Tree,但扫描速度慢于 B+Tree,因为 Tire Tree 要一个个通过遍历回溯重建 key 而 B+Tree 可以直接沿着 leaf node 扫描。
span
:树枝向外分叉的个数,即在树的每层每个节点中 digit 的个数。某层的每个存在的 digit 会有指向其他分支的指针(leaf node 的 digit 指向 tuple),若 digit 不存在则指针处存 null 或其他类似的东西。
- span 被用来约定是否对每个节点进行扇出(fan-out)操作
- n-way Trie = Fan-Out of n(n 路 Trie 表示每个节点最多有 n 条路线,fan-out 为 n,Trie 每层能保存 n 个 digit)

👆优化思路:
- 无需使用空间来标记0和1,直接保存对应的指针。若bit = 0,offset = 0;若bit = 1,offset = 1。
- 水平压缩,减小每个 Trie 结点的 size。
- 当只有一个子结点时,省略所有之后的结点。【又被称为 Patricia Tree、Radix Tree 】
- 垂直压缩,移除了无用分支路径。
- 可能会产生错误,DBMS 经常需要原始的 tuple 看是否能 match key。

2.2 Radix Tree
Radix Tree
:Trie 数据结构的变体【Trie的垂直压缩】。它使用 key 的 digit 逐个检查前缀,而不是比较整个 key。它与 Trie 的不同之处在于,Tadix Tree 的 node 不是 key 中的每个元素,而是表示 key 不同的最小前缀。
- 树的高度取决于 key 的长度,而不是像B+树那样取决于 key 的数量。
- 到 leaf node 的路径表示 leaf 的 key。
Radix tree 是 Trie 的一种特殊版本,没人会在数据库中使用 Trie,用的都是 Radix Tree。
Radix Tree 不同于 B+树,没有标准的方式进行维护,可以采用不同的实现。这里展示一种维护的方式:

并不是所有的属性类型都可以分解为 radix tree 的二进制可比较数字,许多类型的 key 都需要特殊处理:
- Unsigned Integers:对little endian mechines 要把 bits 翻转一遍
- Signed Integers:需要翻转 two’s-complement 从而使得负数小于正数
- Floats:需要分成多个组(neg vs. pos, normalized vs. denormalized),然后存储成 unsigned integer
- Compound:分别转化各个 attributes 然后组合起来
在设计计算机系统的时候,有两种处理内存中数据的方法:
- 大端模式 (big-endian):内存高地址存数据低字节,低地址存高字节。
- 小端模式 (little-endian):内存高地址存数据高字节,低地址存低字节。

3 Inverted Indexes
尽管 tree index 非常有利于点查询和范围查询,如:
- Find all customers in the 15217 zip code
- Find all orders between June 2018 and September 2018
但对于 keyword search,tree index 就显得无能为力,因为要找的是某个属性中的一个子元素。如:
- Find all Wikipedia articles that contain the word "Pavlo"
对 B+Tree、Hash Table 而言,进行关键词搜索的效果都不好。因为关键词搜索是模糊查找,即要找某个属性中的一个子元素。Hash Table 必须使用完整 key 进行搜索;B+Tree 虽然能使用部分 key 进行搜索,但这指的是它将多个属性作为 key,能使用单个或对个属性(部分 key)进行搜索,这要求单个属性的值必须是完整的,不能使用单个属性的一部分进行查找。—— Inverted Indexes
倒排索引(Inverted Indexes)
:存储 word 到 record 的映射,这些 record 的目标属性包含了这些 word。
- 又称为:全文搜索索引 (full-text search index)、concordance。
- 大多数主要的 DBMS 本身都支持倒排索引,但在一些专门的 DBMS 中,倒排索引是唯一可用的表索引数据结构。
全文搜索数据库有很多,比如 ElasticSearch,它是建立在 Lucene 之上的 DBMS,Lucene 是由一个发明了 Hadoop 的人编写的,ElasticSearch 为搜索时用到的索引提供了一个服务端接口工具。
Xapain 是一个 C++ 标准库,也是用来进行全文搜索和索引查找的,比 MySQL 的全文搜索索引效果好。
查询类型(这些都无法在在B+树运行,但可在倒排索引中运行):
- 词组搜索:查找包含给定顺序的 words list 的 record。
- 近似搜索:查找两个 words 在 n 个 words 内出现的 record。
- 通配符搜索:查找包含匹配某些模式(例如,正则表达式)的 words 的 record。
设计决策(如何构建倒排索引):
- 存储什么:索引至少需要存储每条 record 中包含的 words(用标点符号分隔)。它还可以包含其他信息,如词频、位置和其他元数据。
- 最简单的方式就是将这个单词映射到一个 record ID 上,也可以将这个单词周围的其他单词也加进去,这会决定我所能⽀持的查询的复杂程度有多复杂。
- 何时更新:每次修改表的时候更新倒排索引既昂贵又缓慢。因此,大多数DBMS将维护辅助数据结构来 “stage” updates,然后批量更新索引。
更多倒排索引有关信息,可查看 CMU 11-442/11-642 中搜索引擎的内容。
PS:除了 B+Tree 和 Hast Table 外,还存在许多其他可⽤的数据库索引,它们可以做除了点查询以及范围查询外的查询。比如:
- geo-spatial index(地理空间索引):允许进⾏多维度查找,可应用在几何空间场景,常见于视频/图像数据库。数据结构有:R-Tree, Quad-Tree, KD-Tree。
在⼤多数时候,B+Tree 都能满⾜我们的需要,它能⽤于索引,并且⾮常弹性化。
本文作者:Joey-Wang
本文链接:https://www.cnblogs.com/joey-wang/p/16915992.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步