前言
数据库系统中文件索引的工作方式非常类似于书本的索引。假如我们要检索一条记录,数据库首先会查找索引,找到记录所在的磁盘块,然后取出该磁盘块,得到所需的记录。
B
+
B^+
B+树索引采用平衡数(Balance tree)结构,树根到树叶的每条路径的长度都相同。并且在数据插入和删除的情况下仍能保持查询执行效率,是使用最广泛的索引结构之一。
B + B^+ B+树结构(无重复搜索码值1)
下图是一个典型的
B
+
B^+
B+树的结点,它最多包含 n-1 个搜索码值
K
1
,
.
.
.
,
K
n
−
1
K_1,...,K_{n-1}
K1,...,Kn−1,搜索码值是按照一定的顺序排列的(
K
i
<
K
j
,
i
<
j
K_i < K_j, i < j
Ki<Kj,i<j),以及 n 个指针
P
1
,
.
.
.
,
P
n
P_1,...,P_n
P1,...,Pn,指针的作用会在下面解释。
n 对于特定的
B
+
B^+
B+树是固定的,并且还要满足以下条件:
- 叶子结点 的 搜索码值 数量至少为 ⌈ ( n − 1 ) / 2 ⌉ \lceil(n-1)/2\rceil ⌈(n−1)/2⌉,至多为 n − 1 n-1 n−1
- 非叶结点 的 指针 数量至少为 ⌈ n / 2 ⌉ \lceil n/2\rceil ⌈n/2⌉,至多为 n n n
- 根结点 必须至少包含两个指针,除非整棵树只有一个结点
以下是
B
+
B^+
B+ 树实例(n = 4),每一个结点的小方框是指针,大方框存搜索码值。
对于每个结点
- 叶子结点的搜索码值数量至少为 ⌈ ( n − 1 ) / 2 ⌉ = 2 \lceil(n-1)/2\rceil=2 ⌈(n−1)/2⌉=2,至多为 n − 1 = 3 n-1=3 n−1=3
- 非叶结点的指针数量至少为 ⌈ n / 2 ⌉ = 2 \lceil n/2\rceil=2 ⌈n/2⌉=2,至多为 n = 4 n=4 n=4
- 根结点至少包含两个指针
指针的作用
- 对于叶子结点, P i P_i Pi(i < n)指向的是 K i K_i Ki 的具体文件记录。例子中第三层的 P 1 P_1 P1 指向的就是 K 1 K_1 K1 的详细信息。而叶子结点的最后一个结点 P n P_n Pn指向下一个叶子结点,将叶子结点按搜索码的顺序串在一起。(这里的搜索码顺序是字母表顺序)文件所有的搜索码值都要存在于叶子结点中。
- 对于非叶结点,
P
i
P_i
Pi 指向的是搜索码值大于等于
K
i
−
1
K_{i-1}
Ki−1,小于
K
i
K_i
Ki 的子树。例子中第二层的
P
2
P_2
P2 指向的就是这一子树,其搜索码值大于等于
Einstein
小于Gold
。
B + B^+ B+树查询(无重复搜索码值)
从树的根结点开始,向下周游树直到它到达包含指定搜索码值的叶结点。
find()函数伪代码:
function find(value V)
/*假设没有重复码,返回叶子结点C和索引i使得C.Pi指向搜索码值等于V的记录*/
置 C = 根节点
while C 不是叶子节点 {
令 i = 满足 V <= C.K_i 的最小值
if 没有这样的 i {
令 P_m = 结点中最后一个非空指针
置 C = C.P_m
}else if (V = C.K_i){
置 C = C.P_i+1
}else{
置 C = C.P_i
}
}
设 i 是满足 K_i = V 的最小值
if 有这样的 i 存在{
return (C, i)
}else{
return null
}
* B + B^+ B+树(有重复搜索码)
对于叶结点和内部结点中重复的的搜索码值,如果
i
<
j
i < j
i<j,则
K
i
≤
K
j
K_i \le K_j
Ki≤Kj,对于非叶结点,
P
i
P_i
Pi 指向的是搜索码值大于等于
K
i
−
1
K_{i-1}
Ki−1,小于等于
K
i
K_i
Ki 的子树。
因此,在查询的过程中,即使在
V
=
C
.
K
i
V = C.K_i
V=C.Ki 的情况下,也要置
C
=
C
.
P
i
C = C.P_i
C=C.Pi。而且,由此到达的叶子结点
C
C
C 可能仅包含小于 V 的搜索码,因此,必须还要置
C
=
C
C = C
C=C的右兄弟,并且再次检查
C
C
C是否包含 V。
用于在文件中查找记录的属性或属性集叫搜索码。eg,按照书名来找一本书,书名这一属性即为搜索码。而搜索码值就是搜索码的具体值,这里的例子就可以是具体的书名比如《数据库系统原理》。 ↩︎