基本概念
[1] 索引(Index)是帮助数据库高效获取数据的数据结构
[2] 索引分为顺序索引和散列索引, 顺序索引基于值的顺序排序
聚集索引和辅助索引
聚集索引 | 辅助索引 (非聚集索引) |
---|---|
以主键创建的索引 (也叫做主索引) | 以非主键创建的索引 (也叫做二级索引) |
搜索码的排序序列和关系的排序序列相匹配 | 搜索码的排序序列和关系的排序序列不匹配——可以提高不以聚集索引的搜索码作为搜索码的查询的性能, 但是增加了修改数据库的开销 |
在叶子节点存放的表中的数据 | 在叶子节点中存放的是主键和索引列; 查询数据时, 拿到叶子上的主键再去查找相应的数据 (回表) |
非聚集索引在建立的时候也未必是单列的, 可以多个列来创建索引, 但创建多个单列(非聚集)索引的时候, 会生成多个索引树 (所以过多创建索引会占用磁盘空间) | |
如果搜索码不是候选码, 索引只要指向具有该特定搜索码的第一条记录 | 如果搜索码不是候选码, 索引必须包含指向每一条记录的指针 (因为记录是按照聚集索引的搜索码顺序存放的, 具有同一个搜索码的记录可能分布在文件的任何地方) |
实现非候选码的搜索码上的辅助索引: 间接指针层, 指针不直接指向文件, 而是指向一个包含文件指针的桶 |
稠密索引和稀疏索引
[1] 稠密索引为每个搜索码值建立索引, 稀疏索引只对某些搜索码值包含索引项
[2] 只有索引是聚集索引时才能使用稀疏索引
[3] 为每个块建立一个索引是较好的存取时间和空间开销的折中
多码访问
[1] 具有两级和两级以上的索引称为多级索引
[2] 包含多个属性的搜索码称为复合搜索码, 搜索码值按照字典序排序
使用多个单码索引的缺点: 为了得到一个很小的结果集, 必须扫描大量指针
解决: 位图索引 / R树(地理数据类型)
最左匹配原则
[1] 针对:联合索引[2] 最左匹配原则遇上范围查询就会停止, 剩下的字段都无法使用索引。
例如a = 1 and b = 2
a、b字段都可以使用索引, 因为在a值确定的情况下b是相对有序的;
而a > 1 and b = 2
, a字段可以匹配上索引, 但b值不可以, 因为a的值是一个范围。[3]
b = 2
如果建立(a, b)顺序的索引, 匹配不到(a, b)索引;
但是如果查询条件是a = 1 and b = 2
或者a = 1
(又或者是b = 2 and a = 1
)就可以, 因为优化器会自动调整a, b的顺序。
再比如a = 1 and b = 2 and c > 3 and d = 4
如果建立(a,b,c,d)顺序的索引, d是用不到索引的, 因为c字段是一个范围查询, 它之后的字段会停止匹配。
[3] 覆盖索引: 在创建多列索引的时候, 要查询出的列和索引是对应的, 不做回表操作
存储一些不是搜索码属性的属性值以及指向记录的指针
优点: 仅仅使用索引就可以回答一些查询, 减小搜索码的大小, 使非叶结点有更大的扇出, 从而降低索引高度
索引技术的评价
[1] 访问类型、访问时间、插入时间、删除时间、空间开销
[2] MySQL数据库支持多种索引类型, 如BTree索引, 哈希索引, 全文索引等等。
[3] KV存储引擎的索引根据key是否有序, 分为两种:
如果key无序, 则可以采用hash的方法
如果key有序, 则可以采用B+树的方法, 或者类B+树的方法。
索引的更新
插入
[1] 稠密索引:
用出现在待插入记录中的搜索码进行查找, 如果不在索引中就在合适的位置插入索引项; 否则:
- 如果索引项存储的是指向具有相同搜索码所有记录的指针, 就在索引项中增加一个指向新记录的指针;
- 否则索引项存储的是指向具有相同搜索码的第一条记录的指针, 把待插入的记录放到具有相同搜索码的其他记录之后
[2] 稀疏索引: 如果系统创建一个新的块, 将新块中出现的第一个搜索码插入到索引中; 如果这条新插入的记录含有块中最小搜索码, 那么系统更新指向该块的索引项, 否则不做任何改动
删除
[1] 稠密索引: 首先查找要删除的记录, 如果是具有这个搜索码的唯一记录, 就删除相应索引项, 否则:
- 如果索引项存储的是指向具有相同搜索码所有记录的指针, 就在索引项中删除指向被删除记录的指针
- 否则索引项存储的是指向具有相同搜索码的第一条记录的指针, 如果被删除的记录是具有该搜索码的第一条记录, 系统更新索引项使其指向下一条记录
[2] 稀疏索引: 如果索引不包含具有被删除记录搜索码的索引项, 索引不必做任何修改; 否则:
- 如果被删除的记录是具有该搜索码值的唯一记录, 系统用下一个搜索码值的索引记录替换相应索引记录; 如果下一个搜索码值已经有一个索引项, 则删除该索引项 (而不是替换)
- 否则如果搜索码的索引记录指向被删除的记录, 就更新索引项使其指向具有相同搜索码的下一条记录
B+树索引
[1] 结构: 平衡树; 多级索引
[2] 叶结点: 所有的搜索码值都出现在某个叶结点中 (使得B+树称为稠密索引)
非叶结点: 形成叶结点的多级稀疏索引, 结点的指针书称为该结点的扇出, 最少n/2,最多n
根结点包含指针数可以小于n/2
[3] 指针Pn将叶结点按搜索码顺序串在一起, 对文件进行高效顺序处理
[4] B+树保证了结点至少半满; 以随机顺序插入, 那么结点在平均情况下会比2/3更满
查询
[1] 步骤
- 检查当前点, 找到最小的i使得搜索码值Ki大于等于V; 假设找到了这样的值:
如果Ki等于V, 那么将当前结点置为由Pi+1指向的结点;
如果Ki大于V, 那么将当前结点置为由Pi指向的结点 - 如果没有找到这样的Ki值, V大于等于Km-1, 其中Pm为结点中最后一个非空指针。将当前结点置为由Pm指向的结点
重复以上过程知道访问叶结点 - 叶结点如果包含等于V的搜索码, 令Ki为第一个这样的值, 则Pi指向具有搜索码Ki的记录
- 否则失败
[2] 结点大小一般等于磁盘块大小4KB
[3] 与内存中树结构的区别: 结点大, 胖而矮, 读块数少
更新
[1] 插入与分裂
- 孩子指针的分裂介于原始的和新创建的结点之间
位于移动到右边结点的指针之间的搜索码值和指针一起移动
位于留在左边的指针呢之间的搜索码值依然不变 - 确定插入发生的叶结点l, 如果产生分裂, 则将新结点插入结点l的父结点中
[2] 删除与合并
- 查找并删除后, 如果结点太小, 递归从父结点中将它删除, 直到树的根结点
- B+树的非叶结点中的关键码值可能在树的叶结点中并不存在
参与重新分布的兄弟结点越多, 更新的代价越高
[3] 复杂性: 与B+树高度成正比, 一次插入的I/O次数正比于log(n/2)(N), n为结点中指针的最大值, N为文件中记录条数。实际情况:
- 一次查询一般只需要一次或两次I/O操作: 大部分非叶结点在被访问时已经在数据库缓冲区中
- 一次插入需要更新块的I/O操作仅比一次略多: 叶结点分裂的可能性比较小
不唯一搜索码
[1] 一个关系可以拥有多个包含同一搜索码值的记录
两条或者多条记录在索引属性上拥有相同的值, 那么该搜索码称为不唯一搜索码
[2] 问题: 删除记录的效率
解决方案: 通过创建包含原始搜索码和其他属性的复合搜索码确保搜索码唯一
唯一化属性 / 额外属性: 指向记录的指针(记录号), 或者是拥有相同搜索码值的所有记录中值唯一的其他属性
[3] 不唯一搜索码可以在B+树中只存储一次, 并且为该搜索码值维护一个记录指针的桶
优点: 空间上有效
缺点:
- 在叶结点保存桶, 需要额外的代码处理可变长的桶;
- 如果桶存储在单独的块中, 获取记录时可能需要额外的I/O操作;
- 搜索码值出现次数较多, 记录删除效率不高
B+树索引扩展
B+树文件组织
[1] 索引顺序文件组织缺点: 文件增大时, 索引记录存储在溢出块中, 性能下降
[2] B+树文件组织的叶结点存储记录
- 叶结点存储的记录数目比非叶结点存储的指针数目少 (记录比指针大)
- 树中相邻叶结点可能分布在磁盘中的不同位置, 随着不断进行插入和删除操作, 对叶结点顺序访问需要越来越频繁的等待磁盘寻道——解决: 对索引重建
- 存储大型数据对象: 拆分成小的记录序列并组织成B+树, 按序编号, 或者根据记录在大型对象中字节偏移量进行编号, 编号用作搜索码
[3] 辅助索引和记录重定位
- 即使记录没有更新, B+树文件组织也可能改变记录的位置,
例如: 分裂后一些记录移动到新的结点, 所有存储了指向重定位过的记录的指针的辅助索引都必须更新
一个叶结点的分裂可能需要几十甚至几百次I/O操作来更新辅助索引 - 解决方案: 辅助索引不存储指向被索引的记录的指针, 而是存储主索引搜索码属性的值
例如instructor关系上ID为主索引, dept为辅助索引, 辅助索引中与每个系的名字存放在一起的事相应记录中ID值构成的列表, 而不是指向这些记录的指针 - 查询: 首先用堵住索引找到主索引搜索码的值, 然后用住索引找到对应的记录
B+树索引的批量加载
基于项排序+自底向上构建
[1] 索引的批量加载: 将大量项一次插入到索引中
- 创建一个含有关系索引项的临时文件
- 根据构建好的索引的搜索码排序文件, 好处: 所有到特定叶结点的项连续出现, 并且叶结点只需要写出一次
- 扫描排序好的文件并将项插入到索引中
如果B+树开始为空, 批量加载时结点不需要从磁盘中读取, 即便很多项需要插入到一个结点中, 每一个叶结点只需要一次I/O操作
[2] 从叶子级自底向上构建B+树:
- 排序好的项分解到块中, 保证每个块中有尽可能多的项, 由此产生的块形成B+树的叶级
- 每个块中最小值以及指向块的指针用来构建下一级的B+树项, 并且指向块
[3] 建议关系上的索引(除主码上的索引)先删除, 并且插入元组后重新构建索引
B树索引
[1] 在B-Tree中按key检索数据:首先从根结点进行二分查找, 如果找到则返回对应结点的data,
否则对相应区间的指针指向的结点递归进行查找, 直到找到结点或找到null指针, 前者查找成功, 后者查找失败
[2] B树与B+Tree区别:
B+树 | B树 |
---|---|
每个结点指针上限为2d+1 | 每个结点指针上限为2d |
内结点不存储data, 只存储key, B+树中叶结点和内结点一般大小不同 | 不同结点存放的key和指针可能数量不一致,但是每个结点的域和上限是一致的, 所以在实现中B树往往对每个结点申请同等大小的空间 |
叶子结点不存储指针, 数据库系统或文件系统中使用的B+Tree结构增加了顺序访问指针, 形成了带有顺序访问指针的B+Tree, 作用: 提高区间访问的性能 | 只允许搜索码值出现一次, 相应使用更少的树结点 |
查找需要遍历从树根到叶结点的路径 | 不需要遍历到叶结点, 由于B树扇出小, 深度大, 因此查询某些搜索码比B+树块, 另一些慢 |
被删除的项总是出现在叶结点中 | 必须从包含被删除项的子树中选择正确的值来作为替代 |
MySql使用B+Tree而不是BTree索引 |
字符串上的索引和闪存
[1] 字符串上的索引
- 字符串变长: 结点的合并和重新分布取决于结点中空间使用的比例, 而不是结点所能容纳的最大项数
- 字符串很长导致结点扇出降低, 增加树的高度: 使用前缀压缩技术, 只存储搜索码的前缀, 使得这个前缀足以将由该搜索码分开的两棵子树的码值区分开
[2] 闪存
- 通过块组织, B+树索引结构的结点大小比磁盘小得多(因为数据查找快)
- 缺点: 不允许物理层数据就地更新, 每次更新操作转化为整个闪存块的拷贝+写操作, 要求块的旧拷贝随后清除(1ms)