5、重复键的索引

到目前为止,我们都假定作为建立索引基础的查找键是关系的键,所以对任何一个键值,关系中最多有一个记录存在。然而,索引经常用于非键属性,因此有可能一个给定的键对应于多个记录。假如按查找键对记录进行排序,而不管相同键值记录之间的次序,那么,我们可以采用前面介绍的方法来处理不是关系的键的查找键。

对前面方法最简单的扩充是为数据文件建立稠密索引:每一个具有键值K的记录设一索引项。

也就是说,我们允许索引文件中出现重复的查找键。找出所有给定索引键K的记录因此就比较简单:在索引文件中找到具有键值K的第一个索引项,后面紧接着就是其他的具有键值K的索引项,找出它们,然后依据这些索引的指针找出所有具有键值K的记录。

更为有效的方法是为每个键值K只设一个索引项。该索引项的指针指向键值为K的第一个记录。为了找出其他键值为K的记录,需在数据文件中顺序向前查找;在排序的数据文件中,这些记录一定紧跟在所找到的记录后存放。图4-6举例说明了这一想法。


例4.6 假如要找出图4-6中所有索引键值为20的记录。先在索引中找到键值为20的索引项并顺着它的指针找到第一个键值为20的记录。然后在数据文件中继续往前找,由于刚好处于数据文件第二个存储块的最后一条记录,我们就到第三个存储块中去查找。我们发现第三块中第一个记录的键值为20,但第二个记录的键值为30。因此,不用再往前查找;我们已经找到键值为20的两条记录。

100653814.jpg


图4-7为图4-6所示数据文件上的一个稀疏索引。这种稀疏索引是很常见的;它的键-指针对对应数据文件中每个块的第一个查找键。

100707408.jpg


为了在这种数据结构中找出所有索引键为K的记录,我们先找到索引中键值小于或等于K的最后一个索引项E1,然后往索引起始的方向找,直到碰到第一个索引项或碰到一个严格小于键

值K的索引项E2为止。从E2到E1的索引项(含E2和E1)指向所有可能包含查找键为K的记录的数据块。


例4.7 假定我们想在图4-7中查找键值为20的记录。第一个索引块的第三个索引项就是E1,它是符合键值小于等于20的最后一个索引项。我们向后查找,立即找到了键值小于20的索引项。因此,第一个索引块的第二个索引项就是E2。它们相应地指向第二、第三个数据块,正是在这两个数据块中我们找到查找键为20的所有记录。

再举一例。若K等于10,则E1就是第一个索引块的第二个索引项,而E2不存在,因为我们在索引中找不到更小的键值。因此,按照第一个索引项到第二个索引项的指针,我们找到前两个数据块。在这两个数据块中我们就能找到所有键值为10的记录。

一种稍有不同的方式如图4-8所示。图中为每个数据块中新的、即未在前一存储块中出现过的最小查找键设一个索引项。要是在存储块中没有新键值出现,那么就为该块中唯一的键值设一个索引块。在这种方式下,我们查找键值为K的记录可以通过在索引中查找第一个键值满足如下条件之一的索引项。

a)等于K;或者

b)小于K,但下一个键值大于K。

我们按照这个索引项的指针找到相应的数据块。要是在这个数据块中至少找到一个键值为K的记录,那么我们就要继续查找其他数据块,直到找出所有查找键为K的记录。


100736773.jpg


例4.8 假定要在图4-8所示的结构中查找K=20的记录,上述规则指示出第一个索引块的第二个索引项,我们沿着这个索引项的指针找到键值为20的第一个存储块。由于下一个存储块也有键值为20的记录,我们必须继续向前查找。

假若K=30,上述规则指示出第三个索引项。我们沿着它的指针找到第三个数据块,键值为30的记录在这个数据块中开始存放。最后,假若K=25,则上述选择规则的b)部分指出第二个索引项。我们因此来到第二个数据块。如果存在查找键为25的记录,则至少有一个在该块上值为20的记录之后,因为我们知道第三个存储块中新出现的第一个键值是30。既然没有键值为25的记录,我们的查找就失败了。


6、数据修改期间的索引维护

到目前为止,我们都假定数据文件和索引文件由一些连续、装满某种类型的记录的存储块组成。由于随着时间的推移数据会发生变化,我们需对记录进行插入、删除和更新。这势必引起像顺序文件这样的文件组织发生变化,以至于曾经能容纳于块中的记录不再被容纳。我们可以使用3.5节讲述的技术来重新组织数据文件。我们来回忆一下那一部分的三个重要想法。

1)当需要额外的存储空间时,创建溢出块;或当溢出块中记录被删除后不再需要该存储空间时删除溢出块。溢出块在稀疏索引中没有索引项而应该被看作是基本存储块的扩充。

2)若不用溢出块,可以按序插入新的存储块。要是这样做,那么新的存储块就需要在稀疏索引中设索引项。在索引文件中索引项的变动会引起和数据文件的插入与删除同样的问题。要是我们创建新索引块,那么这些索引块必须能以某种方法定位,例如像4节中那样使用另一级索引。

3)当块中没有空间可以插入元组时,有时可移动一些元组到相邻块;相反,当相邻块元组太少时,我们可以合并它们。

然而,当数据文件发生变化后,我们通常必须对索引进行调整以适应数据文件的变化。具体的方法要依赖于索引是稠密还是稀疏以及在上述三种方法中选择哪一个。不过,我们应记住一个一般的原则:

索引文件是顺序文件的一个例子,键-指针对可以看作是按查找键排序的记录。因此,数据文件修改过程中用来维护数据文件的那些策略同样适用于索引文件。

在图4-9中,我们总结了针对数据文件七种不同行为而对稠密索引或稀疏索引所需采取的措施。这七种行为包括:创建或删除空溢出块、创建或删除顺序文件的空块、插入、删除和移动记录。注意,假定空的存储块才能被创建或删除,具体来说,要是我们想删除一个有记录的块,需要先删除其中的记录或把记录移到其他的块。

在这个表中,我们注意到:

(1)创建或删除一个空溢出块对两种索引均无影响。对稠密索引不产生影响是因为索引是针对记录;对稀疏索引不产生影响是因为索引是针对基本存储块而非溢出块。

(2)创建或删除顺序文件的块对稠密索引无影响,这仍是因为索引是针对记录而非存储块;但它对稀疏索引会有影响,因为我们必须为创建或删除的块分别创建或删除一个索引项。

(3)插入或删除记录导致稠密索引上的同一动作,因为记录的相应键-指针对要被插入或删除。

100755720.jpg

然而,这对稀疏索引通常没有影响。例外的情况是当记录是存储块中第一个记录时,稀疏索引中对应块的键值必须被更新。因此,我们在图4-9中相应的更新操作后加注了一个问号,表示这个更新是可能的,但不确定。

类似地,移动一个记录,不论是在块内还是在块间都会引发稠密索引中相应索引项的更新;对稀疏索引而言,则仅当被移动记录是或变成了该块中的第一个记录时才会引发更新操作。

========================为数据变迁所做的准备====================================

由于随着时间的推移,关系或类外延通常会增长。因而较为明智的做法是:不论数据块还是索引块,都为其保留一定的空闲空间。比如说,一开始在每块中只使用75%的空间。这样,在创建溢出块或在块之间移动记录之前,我们能运行一段时间。无溢出块或仅有少量的溢出块的优势在于访问每个记录平均I/O次数仅为1。溢出块数越多,则查找给定记录所需访问的平均存储块数也多。

================================================================================

我们将通过一系列的例子来阐明上述几点所隐含的一组算法。这些例子既包括稀疏索引又包括稠密索引,既包括记录移动方法又包括溢出块方法。


例4.9 首先,让我们来考虑顺序文件的记录删除操作,该顺序文件上建有稠密索引。我们从图4-3所示的文件和索引开始。假设键值为30的记录被删除。图4-10所示为记录删除后的结果。

首先,键值为30的记录从顺序文件中删除。我们假定块外的指针可以指向块内的记录,因而我们不打算将剩余记录40在块中向前移动,而选择在记录30处留下一个删除标记。

在索引中,我们删去键值为30的键-指针对。假定不允许块外的指针指向索引记录,因而没有必要为该键-指针对留下删除标记。所以,我们采取合并索引块的方法,并把后面的索引记录前移。


例4.10 现在,我们来考虑顺序文件上的两个删除操作,该顺序文件上建有稀疏索引。从图4-4所示的文件和索引开始,同样假设键值为30的记录被删除。我们还假定块中记录可前后移动:要么块外没有指针指向记录,要么我们使用图3-17所示的偏移量表来支持这样的移动。

删除记录30后的情形如图4-11所示。记录30已被删除,后面的记录40向前移以使块的前部紧凑。由于40现在是第二个数据块的第一个键值,我们需要更新该块的索引记录。从图4-11中我们看到,与指向第二数据块指针相对应的键值已从30改为40。


100822976.jpg

100824831.jpg


现在,假定记录40也被删除,删除后的情形如图4-12所示。第二个数据块已经没有记录。如果顺序文件是存放在任意的存储块上(例如,不是柱面上连续的存储块),那么我们可以把该块链接到可用空间链表中。

100841469.jpg

要完成记录40的删除,我们还要调整索引。既然第二个数据块不再存在,我们就从索引中删

除其索引项。在图4-12中通过把后面的索引项前移,使第一个索引块紧凑。这一步是可选。


例4.11 现在,我们来考虑插入的影响,从图4-11开始,该图中我们刚刚从一个有稀疏索引的文件中删除了记录30,但记录40还保留着。我们现在插入一个键值为15的记录。通过查看稀疏索引,我们发现该记录属于第一个数据块。但是该数据块已满;它存放着记录10和记录20。

我们能做的一件事是在附近找有空闲空间的块,这样就找到第二个数据块。因此,我们就在文件中往后移动记录,以腾出空间来存储记录15。结果如图4-13所示。记录20从第一个数据块移到了第二个数据块,并且记录15存放到记录20的位置上。为了在第二个数据块中存放记录20和保持记录的顺序,将第二个数据块的记录40往后移并把记录20放在它的前面。

100900734.jpg


最后一步是修改变动的数据块的索引项。我们可能需要修改第一个数据块的键-指针对的键值。不过这里不用,因为插入的记录不是该数据块的第一个记录。然而,我们需要修改第二个数据块的索引项的键值,因为该块的第一个记录的键值原来是40,现在变成了20。

例4.12 例4.11所示策略的问题在于,我们恰好在相邻存储块中找到了空闲空间。要是前面没有删除键值为30的记录,那么我们查找空闲空间的工作只是徒劳。原则上讲,我们不得不把从记录20到数据文件最后的所有记录全部后移,直到我们到达文件末尾并能创建额外的块。

因为有这样的风险,用溢出块为有太多记录的基本块补充空间的做法通常更为明智。图1-14

所示为在图4-11所示结构中插入键值为15的记录后的情形。和例4.11一样,第一个数据块有太多记录。我们不是将记录移动到第二个数据块,而是为第一个数据块创建一个溢出块。可以看到图4-14中每个数据存储都有一个小凸起,它表示块头中存放指向溢出块的指针的空间。任意多个溢出块之间可通过这些指针空间链接起来。

在我们这个例子中,记录15被插入到位于记录10之后的正确位置;记录20为腾出空间被移到溢出块中。因为数据块1的第一个记录没有改变,所以索引项也就不必改变。注意,索引中不存在该溢出块的索引项,因为该溢出块被看成是数据块1的扩充,它不是顺序文件本身的存储块。


100926383.jpg


oracle视频教程请关注:http://u.youku.com/user_video/id_UMzAzMjkxMjE2.html