B+树的概念
规则:
B+跟B树不同B+树的非叶子节点不保存关键字记录的指针,只进行数据索引,这样使得B+树每个非叶子节点所能保存的关键字大大增加
B+树叶子节点保存了父节点的所有关键字记录的指针,所有数据地址必须要到叶子节点才能获取到。所以每次数据查询的次数都一样
B+树叶子节点的关键字从小到大有序排列,左边结尾数据都会保存右边节点开始数据的指针
非叶子节点的子节点数=关键字数(来源百度百科)(Mysql 的B+树)
B+树相比于B树的优点
由于B+树在内部节点上不好含数据信息,因此在内存页中能够存放更多的key。 数据存放的更加紧密,具有更好的空间局部性。因此访问叶子几点上关联的数据也具有更好的缓存命中率;
B+树的叶子结点都是相链的,因此对整棵树的便利只需要一次线性遍历叶子结点即可。而且由于数据顺序排列并且相连,所以便于区间查找和搜索。而B树则需要进行每一层的递归遍历。相邻的元素可能在内存中不相邻,所以缓存命中性没有B+树好。
总结:B+树的I/O次数更少,磁盘读写代价更低,时间更稳定,范围搜索更好
B+树的存在形式
- 结点内有n个元素就会n个子结点;每个元素是子结点元素里的最大值或最小值。
- 结点内有n个元素就会n+1个子结点;最左边的子结点小于最小的元素,其余的子结点是>=当前元素。
B+树实现
以第一种形式实现
B+树节点参数
protected int degree;//阶数
protected BPlusNode<K,V> parent;//父节点
protected int keyNum;//关键字个数
protected LinkedList<K> keys;//存放数据项的list,K为key
protected boolean isLeaf;//是否为叶子节点
LinkedList<BPlusNode<K,V>> childList;//存放孩子节点list
public BPlusNode(int degree){
if (degree<3) throw new IllegalArgumentException("The BPlusTree degree must be at least three!");
this.degree = degree;
parent=null;
keys= new LinkedList<>();
childList= new LinkedList<>();
}
//查找
abstract BPlusNode<K,V> search(K key);
//删除
abstract BPlusNode<K,V> delete(K key);
//插入
abstract BPlusNode<K,V> insert(K key,V value);
//上溢
protected boolean isOverFlow(){
return keyNum >degree;
}
//下溢
protected boolean isUnderFlow(){
return keyNum<Math.ceil(degree/2.0);
}
```java
public V searchData(K key){
Objects.requireNonNull(key);
var search =(LeafNode<K, V>) root.search(key);
if (search.insertIndex<search.entryKey.size()){
var searchEntry = search.entryKey.get(search.insertIndex);
if (searchEntry.key().equals(key)) return searchEntry.value();
}
return null;
}
操作原理
- 查找操作:从根节点开始,将待查找的关键字与当前节点的关键字进行比较。如果待查找的关键字小于当前节点的最小关键字,则沿着最左边的指针进入下一层节点继续查找;如果待查找的关键字大于当前节点的某个关键字,则沿着相应的指针进入下一层节点。重复这个过程,直到找到目标关键字所在的叶子节点。由于叶子节点之间是有序链表,因此在找到叶子节点后,可以方便地在链表中进行顺序查找,以找到所有匹配的记录。
- 插入操作:首先进行查找操作,找到合适的叶子节点位置。如果叶子节点未满,则直接将新的关键字插入到叶子节点中,并保持叶子节点的有序性;如果叶子节点已满,则需要对叶子节点进行分裂操作。将叶子节点平均分成两部分,将中间关键字上移到父节点中,并在父节点中插入一个指向新分裂出的叶子节点的指针。如果父节点也已满,则需要对父节点进行类似的分裂操作,直到找到一个未满的父节点或者到达根节点。如果根节点已满,则创建一个新的根节点,将原来的根节点分裂为两个子节点,并将中间关键字插入到新根节点中。
- 删除操作:先通过查找操作找到要删除的关键字所在的叶子节点。如果该关键字所在的叶子节点中的关键字数量大于最小值(通常为分支因子的一半向上取整),则直接删除该关键字,并保持叶子节点的有序性;如果该叶子节点中的关键字数量等于最小值,且其左右兄弟节点中有一个节点的关键字数量大于最小值,则可以从兄弟节点中借一个关键字过来,或者将兄弟节点与该叶子节点合并。如果兄弟节点的关键字数量也都等于最小值,则需要将该叶子节点与一个兄弟节点合并,并将父节点中对应的关键字删除。如果父节点中的关键字数量小于最小值,则需要对父节点进行类似的调整操作,直到满足 B + 树的结构要求。
优势
- 高效的查找性能:由于 B + 树的高度相对较低,且叶子节点形成有序链表,因此无论是随机查找还是范围查找,都能够在较短的时间内完成。
- 支持顺序访问:叶子节点之间的链表结构使得可以方便地进行顺序访问,这对于需要按照顺序处理数据的应用场景非常有利,如范围查询、排序等。
- 磁盘 I/O 优化:B + 树的节点通常较大,可以容纳多个关键字和指针,使得一次磁盘 I/O 能够读取更多的数据,从而减少磁盘 I/O 的次数,提高系统的整体性能。
应用场景
- 数据库管理系统:用于存储和索引数据库中的表数据,如 MySQL、Oracle 等数据库都广泛使用 B + 树来实现索引结构,以提高数据的查询效率。
- 文件系统:在文件系统中,B + 树可以用于存储文件目录信息,快速定位文件的存储位置,提高文件系统的性能。
MySQL中的B+树是怎么实现的:
MySQL 中的 B + 树主要是通过以下方式实现的:
数据结构定义
- 节点结构:B + 树的节点通常包含多个关键字和对应的数据指针。非叶子节点只存储关键字和指向下一层节点的指针,而叶子节点除了关键字外,还存储了实际的数据记录或者指向数据记录的指针。
- 关键字有序排列:节点中的关键字按照从小到大的顺序排列。这使得在查找、插入和删除操作时能够快速定位到目标关键字的位置。
查找操作实现
- 从根节点开始,将待查找的关键字与节点中的关键字进行比较。
- 如果待查找关键字等于当前节点的某个关键字,则根据该关键字对应的指针找到相应的数据记录。
- 如果待查找关键字小于当前节点的最小关键字,则沿着最左边的指针继续查找下一层节点。
- 如果待查找关键字大于当前节点的最大关键字,则沿着最右边的指针继续查找下一层节点。
- 如果待查找关键字介于当前节点的两个关键字之间,则根据节点中关键字的排列顺序,找到对应的指针,继续查找下一层节点,直到找到目标关键字或到达叶子节点确定不存在该关键字为止。
插入操作实现
- 首先进行查找操作,找到合适的叶子节点来插入新的关键字。
- 如果叶子节点未满,则直接将新关键字插入到合适的位置,保持关键字的有序性。
- 如果叶子节点已满,则需要对该叶子节点进行分裂操作。将叶子节点中的关键字和数据指针平均分成两部分,将中间关键字提升到父节点中,并将新的关键字插入到合适的子节点中。如果父节点也已满,则需要递归地对父节点进行分裂操作,直到找到一个未满的父节点或者到达根节点。如果根节点也已满,则创建一个新的根节点,将原来的根节点分裂成两个子节点,并将中间关键字提升到新的根节点中。
删除操作实现
- 先通过查找操作找到包含要删除关键字的叶子节点。
- 如果要删除的关键字所在的叶子节点中的关键字数量大于最小关键字数量限制,则直接从该叶子节点中删除关键字,并调整关键字的顺序以保持有序性。
- 如果删除关键字后,叶子节点中的关键字数量小于最小关键字数量限制,则需要进行调整操作。可以从相邻的兄弟节点中借调关键字,或者与兄弟节点进行合并操作。如果相邻兄弟节点中的关键字数量足够,则可以将兄弟节点中的一个关键字移动到当前节点中,以保持节点的关键字数量平衡。如果相邻兄弟节点中的关键字数量也不足,则需要将当前节点和兄弟节点合并成一个新的节点,并将父节点中对应的关键字和指针进行相应的调整。如果父节点中的关键字数量小于最小关键字数量限制,可能需要递归地对父节点进行调整操作,直到满足 B + 树的平衡条件为止。
索引组织方式
- 在 MySQL 中,B + 树索引是基于表中的列来创建的。对于不同的数据类型和索引类型,B + 树的实现细节会有所不同。例如,对于整数类型的索引列,关键字的比较和排序操作相对简单直接;而对于字符串类型的索引列,通常需要根据字符集和排序规则来进行比较和排序。
- 聚簇索引和非聚簇索引在 B + 树的实现上也有一些差异。聚簇索引的叶子节点直接存储了数据记录,而非聚簇索引的叶子节点存储的是指向数据记录的指针。因此,在使用聚簇索引进行查找时,可以直接获取到数据记录,而使用非聚簇索引查找时,还需要根据指针再去查找相应的数据记录。
MySQL中索引实现原理:
MySQL 中的索引是一种用于提高查询效率的数据结构,其实现原理主要基于 B + 树等数据结构,以下是详细介绍:
B + 树数据结构基础
- 节点结构:B + 树的节点包含多个关键字和对应的数据指针。非叶子节点仅存储关键字与指向下一层节点的指针,用于引导搜索路径;叶子节点除关键字外,还存储实际的数据记录或指向数据记录的指针。
- 关键字有序排列:节点内的关键字按从小到大的顺序排列,便于快速查找、插入和删除操作时定位目标关键字。
索引的组织方式
- 聚簇索引 :基于表中某一列或多列组合构建。叶子节点直接存储数据记录,数据在物理存储上按聚簇索引列的值顺序排列。一个表只能有一个聚簇索引,通常主键会被自动设为聚簇索引。如
CREATE TABLE
语句中使用PRIMARY KEY
定义主键时,MySQL 会自动创建聚簇索引。 - 非聚簇索引 :叶子节点存储的是指向数据记录的指针,而非数据记录本身。一个表可以有多个非聚簇索引,可基于表中的其他列创建,用于加速对该列的查询。通过
CREATE INDEX
语句创建非聚簇索引,如CREATE INDEX index_name ON table_name (column_name);
。
索引的查找过程
- 从根节点开始,将查询条件中的关键字与节点中的关键字比较。若相等,则根据指针找到对应数据记录;若小于最小关键字,沿最左边指针找下一层节点;若大于最大关键字,沿最右边指针找下一层节点;若介于两个关键字之间,按顺序找到对应指针继续查找,直至找到目标关键字或确定不存在。
索引的插入与维护
- 插入操作:先查找合适的叶子节点插入新关键字。若叶子节点未满,直接插入并保持有序;若已满,则分裂节点,将关键字和指针平均分成两部分,中间关键字提升到父节点,新关键字插入合适子节点,若父节点也满,则递归分裂,直至找到未满父节点或到达根节点,根节点满则创建新根节点。
- 删除操作:先找到包含要删除关键字的叶子节点,若删除后关键字数量大于最小限制,直接删除并调整顺序;若小于最小限制,则需调整,可从相邻兄弟节点借调关键字或合并节点,若兄弟节点关键字数量够,可移动一个关键字到当前节点,若都不足,则合并节点,并调整父节点关键字和指针,若父节点关键字数量不足,需递归调整,直至满足 B + 树平衡条件。
不同数据类型的索引实现特点
- 整数类型:比较和排序操作简单直接,索引查找效率高。
- 字符串类型:需根据字符集和排序规则比较排序,字符集不同排序结果和索引结构可能不同,如 UTF-8 和 GBK 字符集对汉字的编码和排序方式有差异。
索引的优缺点及使用注意事项
- 优点:大大提高查询速度,尤其对大数据量表和复杂查询条件,能快速定位数据,减少磁盘 I/O 操作,提升数据库性能。
- 缺点:占用额外存储空间,索引需存储关键字和指针等信息;插入、更新和删除操作时,需维护索引结构,增加数据操作时间成本。
- 注意事项:并非列都适合建索引,如数据重复度高的列建索引效果差;索引列应选常用于查询条件、连接条件和排序条件的列;定期分析查询语句执行计划,根据实际情况调整索引,以保证索引的有效性。