一、查询处理的代价度量
度量因素(数量):
- 传送磁盘块数:用 t T t_T tT表示
- 搜索磁盘次数:用 t s t_s ts表示
由此,一次传输b个块并进行了S次磁盘搜索的操作将消耗
(
b
t
r
+
S
t
s
)
(bt_r+St_s)
(btr+Sts)秒的时间。事实上
t
s
t_s
ts往往比
t
r
t_r
tr大一个数量级左右,因为其受限于磁头移动的机械操作。
应当注意,这样的估算方法没有将数据写回和cache情况的时间计算在内。
二、选择运算的代价估计
1. 使用文件扫描和索引的选择
总览图:
算法 | 开销 | 原因 | |
---|---|---|---|
1 | 线性搜索 | t s + b r ∗ t T t_s+b_r*t_T ts+br∗tT | 一次初始寻址, b r b_r br(代表文件中的块数量)次块传输 |
2 | 线性搜索,使用码属性等值比较 | aver: t s + ( b r / 2 ) ∗ t T t_s+(b_r/2)*t_T ts+(br/2)∗tT | 与线性搜索相比,因为码属性对应值唯一,搜索到就可以停止,所以平均块传输次数减半 |
3 | B+树主索引,使用码属性等值比较 | ( H + 1 ) ∗ ( t T + t s ) (H+1)*(t_T+t_s) (H+1)∗(tT+ts) | H为树高(根节点高度为0),对从根节点到叶子节点的一条路径都进行一次磁盘访问和磁盘读取 |
4 | B+树主索引,非码属性等值比较 | H ∗ ( t r + t s ) + t s + b ∗ t T H*(t_r+t_s)+t_s+b*t_T H∗(tr+ts)+ts+b∗tT | 除了叶节点之外的每一层进行一次磁盘访问和磁盘读取,在叶结点进行多次读取(其中b是包含指定搜索码记录的块数),因为含有相同的搜索码的块必然相邻,所以直接进行读取即可 |
5 | B+树辅助索引,码属性等值比较 | ( H + 1 ) ∗ ( t T + t s ) (H+1)*(t_T+t_s) (H+1)∗(tT+ts) | 与主索引情况相同(因为假设主索引也是稠密索引) |
6 | B+树辅助索引,非码属性等值比较 | ( H + n ) ∗ ( t T + t s ) (H+n)*(t_T+t_s) (H+n)∗(tT+ts) | 式中n为所取的记录数。非叶子节点的处理与其他索引查询类似,但叶子节点对应的记录所在的块不一定连续,所以必须对每一条记录都进行一次查询(这意味着当n较大时,查询代价较高) |
7 | B+树主索引,广义比较 | H ∗ ( t r + t s ) + t s + b ∗ t T H*(t_r+t_s)+t_s+b*t_T H∗(tr+ts)+ts+b∗tT | 实质上与4中的比较相同 |
8 | B+树辅助索引,广义比较 | ( H + n ) ∗ ( t T + t s ) (H+n)*(t_T+t_s) (H+n)∗(tT+ts) | 实质上与6中的比较相同 |
2.涉及比较的选择
针对形如 σ A ≤ v ( r ) \sigma_{A\leq v}(r) σA≤v(r)的选择,可以使用的方法有:
- 线性搜索
- 使用主索引进行比较
此时,对 ≤ \leq ≤的情况不需使用索引,直接从头线性读取;而对 ≥ \ge ≥情况,先使用索引找到起始位置,再顺序读取到文件末尾 - 使用辅助索引
由于辅助索引并没有建立在搜索键上,所以辅助索引指向的内容并不一定连续,必须对每一条记录进行一次查找块
三、排序运算的代价估计
从文件结构可以了解到,文件内容的修改和文件顺序的修改往往是不同步的,文件会一直维持着逻辑上的有序但不一定是物理上的有序,所以数据库系统需要是不是将文件物理排序以维持文件的物理有序性,这样的排序需要对应的算法。他们主要面临的问题是,内存用于排序的空间可能小于总文件大小,这意味着无法将所有文件都读取到内存中排序,这样的情况需要特殊的排序算法处理:外部归并排序(external merge sort)算法。
Tips:问题的前提条件是 M M M为缓冲区中可用于排序的块数, b r b_r br为需要排序的记录的块数
1. 排序算法的流程描述
- 第一阶段:归并段(run)的建立。其中每个归并段都是排序过的,但其仅仅包含关系中的部分记录。
算法:
i = 0
repeat
读入关系的M块数据或剩下的不足M块的数据
在内存中对关系的这一部分进行排序(可以使用各种方法)
将拍好的数据写到归并段文件Ri中
i = i + 1
until 达到关系末尾
- 第二阶段:对归并段进行归并操作。如果归并段总数N小于M,如此可以为每个归并段文件分配一个块(但显然往往不能全部读入),剩下的至少1个块作为存放结果的输出缓冲块。这一步工作流程如下:
repeat
在所有缓冲块中按序挑选第一个元组
将该元组作为输入写入输出块,并将其从所在块删除
if 输入缓冲块已写满
then 将输出缓冲块写入磁盘
if 任何一个归并段文件Ri对应的缓冲块为空 and Ri没有到达末尾
then 读入Ri的下一块到对应的缓冲块
until 所有的输入缓冲块皆空
将输出缓冲块写入磁盘
可以看到,这样的算法实质上就是N路归并。另外,如果N大于M,并不能一次性归并,就需要次多路归并,这时必须至少留出1块来作为输出缓冲块,所以一趟归并最多处理M-1个归并段,并将产生的新的总结果设为一个新的缓冲段(run)。而经过这样的归并,显然一趟过后,归并段的数目都将减少到原来的1/(M-1)。
2. 排序算法的代价分析
与磁盘相关的操作自然也主要分为2部分:磁盘存取代价和磁盘搜索代价。
-
磁盘存取代价
第一阶段时,要对每个数据块进行一次读取和一次写回(归并段也要先写回磁盘中),所以共需要 2 b r 2b_r 2br次磁盘传输,完成后初始归并段数为 ⌈ b r / M ⌉ \lceil b_r/M\rceil ⌈br/M⌉;对结果进行归并时,由于每一趟归并,归并段的数目都将减少到原来的1/(M-1),所以所需归并趟数为 ⌈ l o g M − 1 ( b r / M ) ⌉ \lceil log_{M-1}(b_r/M)\rceil ⌈logM−1(br/M)⌉,其中正常情况下,每一趟归并对每一个块进行一次读和写。然而我们考虑一种例外:最后一趟可以只产生排序结果而不写入磁盘,于是可以省去 b r b_r br次写磁盘传输。
因此,磁盘块传输的总数: b r ( 2 ⌈ l o g M − 1 ( b r / M ) ⌉ + 1 ) b_r(2\lceil log_{M-1}(b_r/M)\rceil + 1) br(2⌈logM−1(br/M)⌉+1) -
磁盘搜索代价
在产生归并段阶段,要为读取每个归并段的数据作磁盘搜索,也要为写回做磁盘搜索,故这一阶段磁盘搜索次数为 2 ⌈ b r / M ⌉ 2\lceil b_r/M\rceil 2⌈br/M⌉;在归并进行阶段,我们规定将 b b b_b bb个块作为读取归并段的缓冲块,那么每一趟归并需要进行 ⌈ b r / b b ⌉ \lceil b_r/b_b\rceil ⌈br/bb⌉次磁盘搜索(此处省略归并段末尾时读取少于 b b b_b bb个块导致的额外磁盘搜索)。写回时是顺序写回,但如果过写回出和输入归并段在一个块上,磁盘头在写回连续的块的见各种可能已经移到别处,所以也认为是对每趟归并一次读取一次写回(除了最后一趟没有写回);最后,如果输出阶也分配 b b b_b bb个缓冲块时,每一趟归并 ⌊ M / b b ⌋ − 1 \lfloor M/b_b\rfloor -1 ⌊M/bb⌋−1个归并段,因而磁盘搜索总数:
2 ⌈ b r / M ⌉ + ⌈ b r / b b ⌉ ( 2 ⌈ l o g ⌊ M / b b ⌋ − 1 ( b r / M ) ⌉ − 1 ) 2\lceil b_r/M\rceil+\lceil b_r/b_b\rceil(2\lceil log_{\lfloor M/b_b\rfloor -1}(b_r/M)\rceil -1) 2⌈br/M⌉+⌈br/bb⌉(2⌈log⌊M/bb⌋−1(br/M)⌉−1)
四、连接运算的算法的代价估计
连接运算一般指等值连接或自然连接,总体上可以使用 r ⋈ θ s r\bowtie_\theta s r⋈θs进行表示
各种连接方法的总结:
连接方法 | 最坏磁盘传输次数(缓冲区只有2块操作和1块输入缓冲,后同) | 最坏磁盘搜索次数 | 最好磁盘传输次数(缓冲区块数足够容下所有元组,后同) | 最好磁盘搜索次数 |
---|---|---|---|---|
嵌套循环连接(nested-loop join) | n r ∗ b s + b r n_r*b_s+b_r nr∗bs+br:首先要对r中的块全部读取一次,之后对r中的每条元组,都要遍历一遍s中的所有块进而读取每条元组 | n r + b r n_r+b_r nr+br:对每个r中的块都要seek一次,但是读取s中的块时采用顺序读取,所以对一条元组只需要seek一次 | b r + b s b_r+b_s br+bs:一次性将所有元组读入 | 2:只需进行2次seek |
块嵌套循环连接(block nested-loop join) | b r ( b s + 1 ) b_r(b_s+1) br(bs+1):与上差别在于对每一个r中的块而非记录,对s进行一次遍历 | 2 b r 2b_r 2br | b r + b s b_r+b_s br+bs | 2 |
改进后的块嵌套循环连接(共有M块) | ⌈ b r / ( M − 2 ) ∗ b s + b r ⌉ \lceil b_r/(M-2)*b_s+b_r\rceil ⌈br/(M−2)∗bs+br⌉ | 2 ⌈ b r / ( M − 2 ) ⌉ 2\lceil b_r/(M-2)\rceil 2⌈br/(M−2)⌉ | - | - |
索引嵌套循环连接(indexed nested-loop join),这里的所引致的是内层关系的索引 | n r ∗ c + b r n_r*c+b_r nr∗c+br:此时两块操作一块是外层一块是索引。c是使用连接条件对关系s进行单次选择操作的代价,可以根据前述的公式进行估计 | n r ∗ c + b r n_r*c+b_r nr∗c+br:每一次索引IO操作都伴随着一次磁盘访问和磁盘搜索 | ||
排序归并连接(sort-merge join) | b r + b s b_r+b_s br+bs:在主存能够存下 S s S_s Ss的情况下 | ⌈ b r / b b ⌉ + ⌈ b s / b b ⌉ \lceil b_r/b_b\rceil +\lceil b_s/b_b\rceil ⌈br/bb⌉+⌈bs/bb⌉:在为每个关系都分配 b b b_b bb个缓冲块的情况下 |
一部分阐述与解释
- 嵌套循环/块嵌套循环的算法性能可以进一步改进:
- 如果自然连接或等值连接中的连接属性是内层关系的码,则对每一个外层关系元组,内层循环一旦找到了首条匹配元组就可以终止
- 在块嵌套循环连接中,外层关系可以不用磁盘块作为分块的单位,而以内存中最多能容纳的大小为单位,当然同时要留出足够的缓冲空间给内从关系和输出结果使用。在这种情况下,如果内存有M块,就可以从外层关系一次性读取M-2块,当我们读取内从关系中的每一块时,将其与所有的M-2块做连接。这样的改进时内从关系的扫描次数从 b r b_r br减少到 ⌈ b r / ( M − 2 ) ⌉ \lceil b_r/(M-2)\rceil ⌈br/(M−2)⌉次(并且最后有剩下不足M-2块的内容意味着最后一次不到 b s b_s bs的传输),得到了 ⌈ b r / ( M − 2 ) ∗ b s + b r ⌉ \lceil b_r/(M-2)*b_s+b_r\rceil ⌈br/(M−2)∗bs+br⌉的块传输次数和 2 ⌈ b r / ( M − 2 ) ⌉ 2\lceil b_r/(M-2)\rceil 2⌈br/(M−2)⌉的块搜索次数
- 对内层循环轮流做向前向后的扫描。该扫描方法对磁盘块读写请求进行排序,使得上一次扫描时留在缓冲区中的数据可以重用,从而减少磁盘存取次数
- 若内层循环的连接属性上有索引,可以用更有效的索引查找法代替文件扫描法。
- 由于不是顺序访问,索引嵌套循环连接的最坏磁盘访问数和最坏磁盘搜索数都是 n r ∗ c + b r n_r*c+b_r nr∗c+br。并且应该注意到,式中的 c c c这一项如果使用上表的内容,是包含了时间单位 t T 和 t s t_T和t_s tT和ts的,计算时应将 b r b_r br单独×时间。
- 排序归并连接的伪码描述如下:
正常情况下,连接属性相同的集合 S s S_s Ss应当能够全部纳入主存(这也是比较容易实现的,因为一个关系中某个属性的重复次数往往不会太大)。如果对于某些连接属性值, S s S_s Ss大于可用的内存,则可以对 S s S_s Ss与关系r中具有相同连接属性值的元素进行块嵌套循环循环连接。
另外可以看出,归并连接要求关系r和s实现基于连接属性排好序,如果没有排序,算法首先就要对其进行排序。
- 归并链接属性的代价分析
一旦关系已经排序,在连接属性上有相同值的元组是连续存放的,所以已排序的每一元组只需读取一次,因而每一块也只需读取一次,这是归并连接高效的地方。故当假设所有集合 S s S_s Ss都能装入内存时,所需磁盘传输次数就是总块数之和: b r + b s b_r+b_s br+bs
假设为每个关系分配 b b b_b bb个缓冲块,则所需要的磁盘搜索次数为 ⌈ b r / b b ⌉ + ⌈ b s / b b ⌉ \lceil b_r/b_b\rceil +\lceil b_s/b_b\rceil ⌈br/bb⌉+⌈bs/bb⌉。由于磁盘的搜索代价远比数据传输代价高,因此为。个关系分配更多的缓冲块是有意义的
练习:使用如下的例子(关系未排序),计算最坏情况下(只有3块缓冲块可用)和25块缓冲块可用时,进行一次连接所需的代价
注:此处应当注意的是,由于进行归并排序后还有其他行为,将排序结果写回磁盘的代价应当也被计算