查询代价的度量与分析

(一)查询代价的度量

一般来说,我们使用传送磁盘块数以及搜索磁盘次数来度量查询计算计划的代价。

假设:

t_T——磁盘子系统传输一个块的数据平均消耗时间;

t_S——磁盘块平均访问时间(磁盘搜索时间加上旋转延迟);

b——块数;

S——磁盘搜索次数,

那么总的时间开销为b*t_T+S*t_S

当今高端磁盘的典型数值通常是t_S=4mst_T=0.1ms,所以在优化层面上可以根据实际情况(块数和搜索次数)考虑牺牲其中一种时间去换取另一种时间。

 

(二)选择运算

 

(三)排序(外部排序归并算法)

归并排序包括排序和归并两个阶段:

(1)建立多个排好序的归并段。每个归并段都是排好序的,但仅包含关系中的部分记录。

(2)对归并段进行归并。归并段的数目需要小于内存块数,此外剩下的空间还应能容纳结果的一个块。

假设:

M——内存缓冲区中可以用于排序的块数,即内存缓冲区能容纳的磁盘块数;

b_r——包含关系r中记录的磁盘块数;

b_b——归并阶段中为归并段所分配的缓冲块(即每次从归并段读取b_b块数据)。

归并过程如下:

M-1个归并段进行归并得到一个归并段作为下一趟的输入。接下来的M-1个归并段类似地进行归并,如此下去,直到所有的初始归并段都处理过为止。此时,归并段的数目减少到原来的1/(M-1),如果归并后的归并段数目仍大于等于M,则以上一趟归并创建的归并段作为输人进行下一趟归并。每一趟归并段的数目均减少为原来的1/(M-1)。如有需要归并过程将不断重复,直到归并段数目小于M,此时作最后一趟归并,得到排序的输出结果。

代价分析:

在排序阶段要读入关系的每一数据块并写出, 共需2b_r,次磁盘块传输;初始归并段数为\left \lceil b_r/M \right \rceil。由于每一趟归并会使归并段数目减少为原来的1/(M-1),因此总共所需归并趟数为\left \lceil log_{M-1}(b_r/M) \right \rceil。但是最后一趟可以只产生排序结果而不写入磁盘。所以关系外排序的磁盘块传输总数为:2b_r+b_r (2\left \lceil log_{M-1}(b_r/M) \right \rceil-1)=b_r (2\left \lceil log_{M-1}(b_r/M) \right \rceil+1)

在排序阶段,我们需要2\left \lceil b_r/M \right \rceil次磁盘搜索以读取和写回数据;在归并阶段,每一趟归并需要作2\left \lceil b_r/b_b \right \rceil次磁盘搜索以读取和写回数据,除了最后一趟以外(因为我们假定最终结果不写回磁盘)。则磁盘搜索的总次数为:2\left \lceil b_r/M \right \rceil +\left \lceil b_r/b_b \right \rceil(2\left \lceil log_{M-1}(b_r/M) \right \rceil-1)

 

(四)连接运算

(1)嵌套循环连接

假设关系r为连接的外层关系,关系s为连接的内层关系,那么可以通过两层的嵌套循环来实现t_rt_s进行拼接(t_rt_s分别表示rs的元组)。

算法表示如下:

for each 元组t_r in r do begin
    for each 元组t_s in s do begin
        测试元组对(t_r, t_s)是否满足连接条件;
        如果满足,把t_r·t_s加到结果中;
    end
end

假设:

r——外层关系;

s——内层关系;

n_r——r中的元组数;

n_s——s中的元组数;

b_r——r中的磁盘块数(b_r\leq n_r);

b_s——s中的磁盘块数(b_s\leq n_s)。

(a)在最坏的情况下,即缓冲区只能容纳每个关系的一个数据块(两个关系都只能有一个块在内存),

对于每次外层循环:

都需要取出外层关系r中的一个块以及内层关系s中的所有块,所以需要n_r*b_s+b_r次块传输。

但读取s都只需要一次磁盘搜索(因为是顺序读取),读取r一共需要b_r次磁盘搜索,所以总的磁盘搜索次数为n_r+b_r

(b)在最好的情况下,即内存有足够的空间同时容纳每个关系的所有块,

此时每一个数据块只需读一次,即只需b_r+b_s次块传输。

磁盘搜索也只需开始读取关系时的两次(放入内存后遍历则无需使用磁盘搜索)。

(c)如果其中一个关系能完全放入内存中,那么把这个关系放进内层是有好处的,因为这样每次外层循环中内层关系只需要取一次。

 

(2)块嵌套循环连接

因缓冲区太小而内存无法完全容纳任何一个关系时,以块的方式而不是以元组的方式处理关系,可以减少不少块读写次数。

算法表示如下:

​for each 块B_r of r do begin
    for each 块B_s of s do begin
        for each 元组t_r in B_r do begin
            for each 元组t_s in B_s do begin
                测试元组对(t_r, t_s)是否满足连接条件
                如果满足,把t_r·t_s加到结果中
            end
        end​
    end
end

假设:

r——外层关系;

s——内层关系;

b_r——r中的磁盘块数(b_r\leq n_r);

b_s——s中的磁盘块数(b_s\leq n_s)。

(a)在最坏的情况下,即缓冲区只能容纳每个关系的一个数据块(两个关系都只能有一个块在内存),

块传输次数为b_r*b_s+b_r;磁盘搜索次数为2b_r。(即使用b_r替换n_r

如果内存不能使容纳任何一个关系时,则使用较小的关系作为外层关系更有效。

(b)在最好的情况下,即内存有足够的空间同时容纳每个关系的所有块,

块传输次数:b_r+b_s;磁盘搜索次数:2。

(c)优化:外层关系可以不用磁盘块作为分块的单位,而以内存中最多能容纳的大小为单位,当然同时要留出足够的缓冲空间给内层关系及输出结果使用。也就是说,假设内存有M块,则一次读取外层关系中的M-2块。

块传输次数:\lceil b_r/(M-2)*b_s+b_r) \rceil;磁盘搜索次数:2\lceil b_r/(M-2) \rceil

 

(3)索引嵌套循环连接

对于外层关系r中的每一个元组t_r,可以利用索引查找s中和元组t_r满足连接条件的元组。

假设:

n_r——r中的元组数;

b_r——r中的磁盘块数(b_r\leq n_r);

b_s——s中的磁盘块数(b_s\leq n_s);

t_T——磁盘子系统传输一个块的数据平均消耗时间;

t_S——磁盘块平均访问时间(磁盘搜索时间加上旋转延迟);

c——使用连接条件对关系s进行单次选择操作的代价。

读取关系r需要b_r次I/O操作,每次I/O操作需要一次磁盘搜索和一次块传输。对于关系r中的每个元组,在s上进行索引查找。这样,连接的时间代价可用b_r(t_T+t_S)+n_r*c来计算。

代价计算公式表明,如果两个关系rs上均有索引时,一般把元组较少的关系作外层关系时效果较好。

 

(4)归并连接

归并连接算法为每个关系分配一个指针,这些指针一开始指向相应的关系的第一个元组,随着算法的执行指针遍历整个关系。归并连接算法要求每个关系对于连接属性是已经排好顺序的,而且在连接属性上具有相同值的元组集合必须能够全部存放在内存中。

算法表示如下:

C := 连接属性;
pr := r的第一个元组的地址;
ps := s的第一个元组的地址;
while (ps != null and pr != null) do begin
    t_s := ps所指向的元组;
    S_s := {t_s};
    让ps指向关系s的下一个元组;
    done := false;
    while (not done and ps != null) do begin
        t_s1 := ps所指向的元组;
        if (t_s1[C] == t_s[C]) then begin
            S_s := S_s union {t_s1};
            让ps指向关系s的下一个元组;
        else 
            done := true;
        end
    t_r := pr所指向的元组;
    while (pr != null and t_r[C] < t_s[C]) do begin
        让pr指向关系r的下一个元组;
        t_r := pr所指向的元组;
    end
    while (pr != null and t_r[C] == t_s[C]) do begin
        for each t_s in S_s do begin
            将 t_s join t_r加入结果中;
        end
        让pr指向关系r的下一个元组;
        t_r := pr所指向的元组;
    end
end

假设:

b_r——r中的磁盘块数;

b_s——s中的磁盘块数;

b_b——内存为每个关系(归并段)分配的缓冲块数量。

(a)一旦关系已排序,在连接属性上有相同值的元组是连续存放的。所以已排序的每一个元组只需读一次,因而每个块也只须读一次,所需磁盘块传输次数是两个文件的块数之和:b_r+b_s,所需磁盘搜索次数为\lceil b_r/b_b \rceil+\lceil b_s/b_b \rceil

(b)假设两个关系未排序,则需要先对关系rs分别进行归并排序。另假设内存大小为M个磁盘块。

由(三)可知:

对关系r进行排序需要b_r(2\lceil \log_{M-1}(b_r/M)\rceil+1)次块传输和2\lceil b_r/M \rceil + \lceil b_r / b_b \rceil(2\lceil \log_{M-1}(b_r/M)\rceil-1)次磁盘搜索,外加将结果写回时的b_r次块传输和\lceil b_r / b_b \rceil次磁盘搜索;

对关系s进行排序需要b_s(2\lceil \log_{M-1}(b_s/M)\rceil+1)次块传输和2\lceil b_s/M \rceil + \lceil b_s / b_b \rceil(2\lceil \log_{M-1}(b_s/M)\rceil-1)次磁盘搜索,外加将结果写回时的b_s次块传输和\lceil b_s / b_b \rceil次磁盘搜索;

最后,归并这两个关系需要的b_r+b_s次块传输和\lceil b_r/b_b \rceil+\lceil b_s/b_b \rceil次磁盘搜索。

所以,总的块传输次数为:

b_r(2\lceil \log_{M-1}(b_r/M)\rceil+1)+b_s(2\lceil \log_{M-1}(b_s/M)\rceil+1)+2 (b_r+b_s)

总的磁盘搜索次数为:

2\lceil b_r/M \rceil + \lceil b_r / b_b \rceil(2\lceil \log_{M-1}(b_r/M)\rceil-1)+2\lceil b_s/M \rceil + \lceil b_s / b_b \rceil(2\lceil \log_{M-1}(b_s/M)\rceil-1)+ 2(\lceil b_r/b_b \rceil + \lceil b_s/b_b \rceil)

因此,通过为每个归并段分配更多的缓冲块,磁盘搜索的次数能够显著地减少。

 

(五)散列连接

散列连接算法的基本思想是把这两个关系的元组划分成在连接属性值上具有相同散列值的元组集合。如下图所示:

对于以上的划分,r_i中的元组t_r只需与s_i中的元组t_s相比较,而没有必要与其他任何划分里的元组t_s相比较。即假设c是关系r中的元组,d是关系s中的元组,h是连接属性上的散列函数,那么只有在h(c)=h(d)dc才须比较。若h(c)\neq h(d),则dc在连接属性上的值必不相等。然而,如果h(c)=h(d),则必须检查dc在连接属性上的值是否相同,因为可能实际值不同却有相同的散列值。

算法表示如下:

C := 连接属性;
/* 对关系s进行划分 */
for each元组t_s in s do begin
    i := h(t_s[C]);
    H_s_i := H_s_i union {t_s};
end
/* 对关系r进行划分 */
for each元组t_r in r do begin
    i := h(t_r[C]);
    H_r_i := H_r_i union {t_r};
end
/* 对每一划分进行连接 */
for i := 0 to n_k do begin
    读H_s_i,在内存中建立其散列索引;
    for each元组t_r in r_i do begin
        检索s_i的散列索引,定位所有满足 t_s[C] = t_r[C] 的元组t_s;
        for each匹配的元组t_s in H_s_i do begin
            将 t_r join t_s 加入结果中;
        end
    end
end

从以上伪代码可知,该算法先为每个s_i构造散列索引,然后用r_i中的元组进行探查(即在s_i中查找)。关系s称为构造用输入(build input),关系r称为探查用输入(probe input)。

假设:

b_r——r中的磁盘块数;

b_s——s中的磁盘块数;

n_h——桶数目;

M——内存大小,即内存所能容纳的磁盘块;

b_b——内存为每个关系(归并段)分配的缓冲块数量。

(a)假设n_h的值小于内存块数M

关系rs的划分需要分别进行一次完整的读入与写回,该操作需要2(b_r+b_s)次块传输。在构造和探查阶段每个划分杜如一次,这又需要(b_r+b_s)次块传输。由于每个划分都有可能有一个部分满的块,而这个块需写回、读入各一次,因此对于rs而言存取这些部分满的块至多增加2*n_h次开销。从而总的块传输次数为3(b_r+b_s)+4n_h)

划分总共需要2(\lceil b_r/b_b \rceil + \lceil b_s/b_b \rceil)次磁盘搜索。在构造和探查阶段,每个关系中n_h个划分中的每一个仅需一次磁盘搜索,因为每一个划分都可以顺序地读取。因此需要2(\lceil b_r/b_b \rceil + \lceil b_s/b_b \rceil)+2n_h次磁盘搜索。

由于n_h的开销跟b_rb_s相比是很小的,所以可以忽略。

(b)如果n_h的值大于或等于内存块数M,因为没有足够的缓冲块,所以关系的划分不可能一趟完成,需要重复多趟。在每一趟中,输入的最大划分数不超过用于输出的缓冲块数。每一趟产生的存储桶在下一趟中分别被读入并再次划分,产生更小的划分(相当于构建多级索引)。这种划分方法称为递归划分。

递归划分的每一趟可将划分的大小减为原来的1/(M-1),不断重复操作直到每个划分最多占M块为止,那么划分关系s所需的趟数为\lceil \log_{M-1}(b_s)-1 \rceil

块传输次数:2(b_r+b_s)\lceil \log_{M-1}(b_s)-1 \rceil+b_r+b_s

磁盘搜索次数:2(\lceil b_r/b_b \rceil + \lceil b_s/b_b \rceil)\lceil \log_{M-1}(b_s)-1 \rceil

 

(以上笔记整理自《数据库系统概念(第6版)》)

  • 9
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值