CMU15445 Lecture 7 & 8 Tree Indexes
回顾
上节提及了许多数据库内部使用的数据结构,它们可以用来存储内部元数据,存储核心数据,临时数据结构,实现表的索引,表的索引,可能涉及范围查询
表的索引(Table Indexes)
定义
table index是表的某些列(某些attribute)的一个复制,并包括一个指向某些tuple的标识,比如主键,或者指向tuple的指针。这些索引一般按复制的attribute被排序了。DBMS可以通过这些索引高效的找到对应的tuple,并且需要保证table与index的对应内容逻辑上是一致的。
这里存在一种权衡,如果建立的索引较多,那么取数据也更快,但是需要更多的内存以及更高的维护成本,DBMS需要决定最好的索引以执行最优的查询,是DBMS来使用这些索引,用户(也就是写select的那些的人)
B+Tree
定义
B+Tree是一种自平衡树,它可以使得内部数据有序,并且支持在O(logn)的时间完成查找,线性访问,插入,删除操作。它是disk-oriented DBMS’s用来针对读写大量数据的一种优化
B树与B+树的区别
B树并不支持线性遍历,因为其会将所有的key/value存在所有的节点中,而B+树对于非叶子节点存的是下一层孩子的地址与key,只有叶子节点才存key/value对。对于DBMS而言,B+树索引的键值分别表中某些列(某些attribute)的一个复制与是指向某些tuple的标识
每一个B+树的node(可以存很多个数据),一般会按照key排好序
B+树的结构
一颗M路B+树得满足以下条件:
- 它必须得是完全自平衡的,也就是说每个叶子节点都在同一层
- 每一个内部节点,除了根节点,都必须是半满的,也就是满足M / 2 - 1 <= key的数量 <= M - 1,也就是说其最多有M个孩子,最少有M / 2个孩子
- 内部节点k个key,就有k + 1个孩子
对于叶子节点的value,有两种方式:
- Record/Tuple Ids:存储指向最终 tuple 的指针
- Tuple Data:直接将 tuple data 存在 leaf node 中,但这种方式对于 Secondary Indexes 不适用,因为 DBMS 只能将 tuple 数据存储到一个 index 中,否则数据的存储就会出现冗余,同时带来额外的维护成本。Secondary Indexes大概的意思是说,除了primary key以外的index,如果存在这种Secondary Indexes,这是时候index使用Tuple Data的方法存储,将会造成index中数据存储的冗余。
B+树的操作
插入
- 找到正确的叶子节点 L
- 以正确的顺序将新的条目插入 L中
- 如果L有足够的空间,那么Insertion结束
- 否则将需要将 L 分裂成两个节点,同时在 parent node 上新增 entry,若 parent node 也空间不足,则递归地分裂,直到 root node 为止。平均的分配的分配
- 对于Inner node的分裂,需要把中间值向上抛
Selection Conditions
B+树能够做到,即使所给key的attribute不完整,任旧可以完成搜索
DELETE
当一个节点删除到entry小于half-full的状态时,需要合并
- 找到需要删除的entry所处的叶节点L
- 如果删除后,L的entry的数目不小于half-full,那么删除结束,
- 否则需要从其他叶子节点借entry
- 如果有两个叶子节点的entry数量都小于half-full,那么合并它们
- 如果发生了合并,需要删除L的parent中指向L的entry
有些数据库会延迟合并,以减少I/O操作的数量
Non-Unique Indexes
- 重复的key如何解决
- 将record IDs附加到key中
- 在存在重复key的entry的叶子节点后面在添加一个溢出节点
Clustered Indexes(聚簇索引)
简单地说,聚簇索引就是将数据与索引放到一块,类似于这个?
而非聚簇索引就是数据与索引不在一块,比如索引的value存的时tupl的页号与slot号
B+树的设计
节点的大小
对于OLTP,事务性数据库适合点查询,也就Root-to-Leaf Traversals;对于OLAP数据库,适合bigger的页,适合 Leaf Node Scans
合并阈值
B+树删除entry后,可以选择延迟合并,对于下溢的节点,先不合并,可以选择周期性的批量合并
变长key的处理方式(Variable Length Keys)
- 用指针:Instead of storing the keys directly, we could just store a pointer to the key
- 直接允许Nodes本身变长,但这个会使得Node与页大小的对应发生变化,比如一个节点需要好几个页
- Padding
- key map/Indirection
节点内部的查找
一个叶节点可能包含数千个entry
- 线性
- 二分查找
- interpolation,可以理解为推测,也就是通过节点的元数据来得到所需的key
优化
- 前缀压缩, 就是对于具有相同前缀的key,将这个前缀只存一次,不过存储结构会变得很奇怪吧?
- 去重复,这个和前面的Non-Unique Indexes与Duplicate Keys是不是重复了?
- suffix truncation
由于 inner nodes 只用于引导搜索(direct traffic),因此没有必要在 inner nodes 中储存完整的 key,我们可以只存储足够的 prefix 即可,这个具体实现不是很好想象,适合于频繁插入的场景,如下图所示:
- Bulk Insert
构建二叉树,最快的方法是,先将key排序,再自底向上构建Inner nodes;对于大量连续的插入操作也可以这样做,先排成一个序列,在插入树中 - Pointer Swizzling
没看懂,是说,不通过page_id来访问buffer pool中的页,而是通过指向buffer pool的指针来访问page吗?那latch的争夺怎么处理,可以在索引上加锁
Index的更多用处
隐式索引
对于
- primary keys
- unique constriants
- foreign keys
DBMS会自动创建隐式索引
Partial Indexes
只对table的子集建立index,比如这里只对c = 'WuTang'的tuple建立索引
Covering Indexes
INDEX INCLUDE COLUMNS
可以在索引中加入别的colum,但是不把它当成是key的一部分
Function/Expression Indexes
EXTRACT() 函数用于返回日期/时间的单独部分,比如年、月、日、小时、分钟等等。
Trie Index
radix Index
radix Index就是优化压缩版的Trie index
这里的positive是指匹配上了,但是匹配上了有可能会找到错误的tuple,比如在下面可以找到key = 10的tuple,当拿key = 2(00000000 00000010),任旧可以匹配到key = 10的tuple,但这时是错误的tuple,真实情况是key的tuple不存在
Inverted Index
尽管 tree index 非常有利于 point 和 range 查询,但是对于key word 查询,tree index就力不从心了
Inverted Index就是keyword与包含keyword的目标atrribute的records的集合
倒排索引可以完成以下的操作
一些问题
- 就是节点的层数level,slots就是一个用来记录页内存是否空闲的数组
- hash表有个缺点,就是其天然不分块,17:10,如需要将hash表放到硬盘,需要考虑如何将hash表分块,而b+树可以一个叶节点占一个页,假定HASH表项与B+树叶节点的数据大小一致,当一个B+树节点能够正好放满一个页,这时候HASH的表的表项大概率不会放到一个页中
- 这两个是不是重复了?
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· DeepSeek 开源周回顾「GitHub 热点速览」