曾经沧海难为水,除却巫山不是云。|

Joey-Wang

园龄:4年3个月粉丝:17关注:0

2022-11-22 18:12阅读: 56评论: 0推荐: 0

Lecture#08 Tree Indexes2

review:

上节课我们说对于非唯一性索引(有重复的key):

  • Duplicate Keys:使用相同的叶结点布局,但存储 Duplicate Keys 多次。(更常用)
  • Value Lists:每个键 key 存储一次,并维护一个唯一的 value 的链表。

但在B+树中,实际应如何维护这些重复的索引(key)呢?有两种方法:

1️⃣ 通过在 key 后面追加对应 tuple 的 record id 让重复的 key 变得各不相同。

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

2️⃣ 将重复的 key 保存在 overflow 的叶子结点上。

image-20221013023307413
  • 这种方式违反了 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都会自动为主键做唯一性约束,因此会自动创建索引。但对参照完整性约束(外键),则不会自动创建索引。
image-20221013035215712

部分索引:在整个表的一个子集上创建索引。这潜在地减少了规模和维护它的开销。

  • 这种方式非常常见 🌰 可以通过不同索引将不同日期范围的数据分开,在每个月份上建立索引,这样可以以想要的顺序在该月份中进行快速查找。
  • 使用一个部分索引可以避免一堆不需要的数据去污染 Buffer Pool,同时树的高度也会变得更低,这样就有利于更快的查找。
image-20221013035727989

覆盖索引:若处理查询所需的所有属性都能在索引中获取,则 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 变得很大。
image-20221013042049806

函数/表达式索引:将函数或表达式的输出存储为 key,而不是原始值。即通过 key 衍生出来的某些值而不是通过 key 自身的值来进行查找。

  • 识别哪些查询可以使用该索引是 DBMS 的工作。
image-20221013044253056

所有DBMS支持隐式索引,大部分支持部分索引,一部分支持覆盖索引,支持索引包含列的更少。

创建索引后,DBMS可以自己从不同的各种索引中找到最好的访问方法并使用。

创建索引时,DBMS默认使用 B+tree,除非强制指定索引类型,比如 Hash Table。

2 Radix Tree

B+树存在的问题:

  1. B+树中 key 会被多次复制,要判断某个key是否存在于表中,必须遍历到 leaf node。因为 inner node 可能会保存那些不再存在于 tree 中的 key 的拷贝,它们要用这些 key 当作路标。
  2. 如果 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。
image-20221013195202758

Trie Tree 的特点:

  1. Trie Tree 的形状取决于 key 的分布以及长度,它是一个确定的数据结构,不管插入 key 的顺序如何,最终都会得到相同的物理数据结构(即结点布局)🆚 B+ Tree 的高度取决于 key 的数目,且结点布局与 key 的插入顺序有关,这取决于 B+Tree 如何进行拆分与合并
  2. Trie Tree 不用进行重新平衡(垂直层面可以进行 rebalance,水平层面不需要)🆚 B+Tree 需要重新平衡
  3. Tire Tree 中操作复杂度都为 O(K),K 为 key 的长度 🆚 B+Tree 中操作复杂度都为 O(logn)
  4. 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)
image-20221013203317349

👆优化思路:

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

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+树,没有标准的方式进行维护,可以采用不同的实现。这里展示一种维护的方式:

image-20221013212150970

并不是所有的属性类型都可以分解为 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):内存高地址存数据高字节,低地址存低字节。
image-20221013213222545

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。

设计决策(如何构建倒排索引):

  1. 存储什么:索引至少需要存储每条 record 中包含的 words(用标点符号分隔)。它还可以包含其他信息,如词频、位置和其他元数据。
    • 最简单的方式就是将这个单词映射到一个 record ID 上,也可以将这个单词周围的其他单词也加进去,这会决定我所能⽀持的查询的复杂程度有多复杂。
  2. 何时更新:每次修改表的时候更新倒排索引既昂贵又缓慢。因此,大多数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 中国大陆许可协议进行许可。

posted @   Joey-Wang  阅读(56)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
展开