数据库系统-学习记录11

ch4.查询执行

物理查询计划操作符

  物理査询计划由操作符构成,每一个操作符实现计划中的一步(不理解物理操作符到底都有些啥)

物理操作符计算模型

  在物理操作符计算模型中,也是用磁盘I/O数目去衡量每个操作的代价的标准,理由同前:从磁盘中得到数据的时间明显长于在内存中的任何操作

衡量代价的参数

  使用B( R R R)来描述一个关系R的所有元组所需的块的数目

  使用T( R R R)来描述一个关系R的元组总数

  使用V(R, a)来描述关系R上属性a取不同值的数目

实现物理操作符的迭代器

  将关系视作一个类,令其具有三个方法:Open()、GetNext()和Close(),则可以允许使用者通过调用这些方法,来完成物理操作符的功能,并且调用一次返回一个元组。关于三个方法的详细描述如下(三个方法的集合称为迭代器,这名字怎么都觉得非常抽象……无法理解。但不影响后面的理解):
  1、Open():初始化,启动获得元组的过程
  2、GetNext():返回结果中的下一个元组,如果无法再返回元组,则返回NotFound
  3、Close():在获得所有的元组后,终止迭代

  例如,对于表扫描的操作符(将关系中的元组全部获取一遍),可以通过对上述三个方法进行如下定义来实现:

Open(){
    b := R的第一块;
    t := b的第一个元组;
}

GetNext(){
    IF(已超过块b的最后一个元组){
        将b前进到下一块;
        IF(没有下一块){
            RETURN NotFound;
        }
        ELSE{ /* b是一个新块 */
            t := 块b上的第一个元组;
        }
    } /* 现在我们已准备好返回t并前进 */
    oldt := t;
    将t前进到b的下一元组;
    RETURN oldt;
}
    
C1ose(){
}

  类似地,其他的操作符也可以通过分别定义这三个方法来实现

一趟算法

  操作符可以被分为三大类:
  1、一次处理单个元组的一元操作符
  2、一次处理整个关系的一元操作符
  3、一次处理整个关系的二元操作符

  而即便是同种操作符,它的实现方式也可能因数据大小的不同而不同

  如果某方法仅需要从磁盘读取一次数据,则称此方法为一趟(one-pass)算法。而如果数据太大,需要从磁盘先读取一次,处理后再写入回去,随后再读取第二次,则称此方法为两趟算法。从两趟算法,可以自然推广到多趟算法

一次单个元组操作的一趟算法

  对于运算符 σ ( R ) \sigma(R) σ(R) π ( R ) \pi(R) π(R),只需将关系R直接输入到缓冲区,操作后再移至输出缓冲区即可。而即便是关系R无法直接被内存容纳,也可以读出R的一块输入到缓冲区,对每一个元组进行操作后再移至输出缓冲区。因此,缓冲区的大小M只要 ≥ 1 \ge 1 1就可以了

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sJxOnCvx-1605536086166)(C:\Users\蔡三圈\AppData\Roaming\Typora\typora-user-images\image-20201116192641261.png)]

  一般情况下,如果关系R是聚集(都存在一起)的,则代价为B( R R R);而如果关系R不是聚集的(分散在各个块中),则代价为T( R R R)(输出缓冲区不算在所需空间内)

整个关系的一元操作的一趟算法

  算符消除重复 δ \delta δ和分组 γ \gamma γ是作用在整个关系上的(如果作用在单个元组上,则是没有意义的)

  消除重复

  为了消除重复,可以一次一个地读取R的每一块,但是对每一个元组,都需要判定:
  1、这是第一次看到这个元组,这时将它复制到输出
  2、之前见过这个元组,这时对它进行舍弃

  为了满足这样的判定,就需要将缓存(大小为M)划分出用于保存输出的元组的部分。可以只使用一个内存缓冲区保存R的元组的一个块,而剩余的M-1个缓冲区用于保存目前已经见过的元组的副本:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OG3P4Wxn-1605536086169)(C:\Users\蔡三圈\AppData\Roaming\Typora\typora-user-images\image-20201116194055701.png)]

  而由于如果直接在缓冲区中存入元组,会过于浪费“判断一个元组是否出现过”的时间。因此,应该使用一种主存结构(例如具有大量桶的散列表、平衡二叉查找树等),使得能够快速辨别一个给定的元组是否存在

  如果目标是要让M-1个缓冲区可以装下所有“第一次见到”的元组,则应该满足的条件为: B ( δ ( R ) ) ≤ M − 1 B(\delta (R))\le M-1 B(δ(R))M1

  分组

  分组运算的缓冲区也需要划分M-1块出来,用于保存各组的信息:分组属性的值、每个聚集(组)的一个或多个累计值(AVG函数需要同时保存组内元组个数以及该组元组的值的总和)

二元操作的一趟算法

  并、交、差、积和连接,均为二元操作。在二元操作时,可以将长度较短的关系先存入到划分出的M-1个缓冲区中,再开始遍历长度较长的关系,从而进行各种运算。因此,应该满足条件: min ⁡ ( B ( R ) ,   B ( S ) ) ≤ M \min(B(R),\ B(S))\le M min(B(R), B(S))M,且磁盘I/O数为B( R R R)+B(S)

集合并

  将较短的集合S读入到M-1个缓冲区(并复制到输出区)后,建立一个关键字为整个元组的查找结构,使其能够在输入元组后,快速查找是否有相对应的元组。随后,用剩下的那个缓冲区,一次一块地读取R,对块中的每一个元组t,都去查找t是否存在于S中,如果不在则输出,否则跳过

集合交

  同理,只不过条件改为“如果存在则输出,否则跳过”,最后结果即为输出的元组

集合差

  同理,需要注意的是集合差是R-S还是S-R,二者是有区别的

包交、包差

  同上,只不过需要增加一项:计数(因为包中元组可以重复,所以需要有计数)

  同理,将较短的关系S读到M-1个缓冲区中,使用第M个缓冲区遍历R中元组t时,分别与S中的所有元组进行连接,连接后即输出

自然连接

  在将较短关系S读到M-1个缓冲区的时候,保存的是以Y的属性为查找关键字的内存查找结构。随后,使用第M个缓冲区遍历R的每一个元组t,在S中找到所有属性上相符合的元组,随后连接并输出

嵌套循环连接

  两个元组之间的连接,最直接的便是基于元组的嵌套循环链接:

for S中的每个元组s do
    for R中的每个元组r do
        if r与s连接形成元组t then
            output t;

  而直接使用此方式连接时,最大可能需要T( R R R)T(S)次磁盘I/O,这效率显然是可以改进的。考虑如下的基于块的嵌套循环连接算法:

for S中每个大小为M-1块的chunk
    将这些块读入主缓冲区中;
	将其元组组织为查找结构,查找关键字是R和S的公共属性;
	for R的每个块b
        将b读入主存;
		for 块b的每一个元组t do begin
            找出S在主存中的元组中那些能与t连接的元组;
			输出t与这些元组中每一个的连接;
		end;
    end;
end;

  可以将磁盘I/O的次数优化至B(S)(M-1+B( R R R))/(M-1)次,注意到当B(S)小于B( R R R)时,磁盘I/O次数明显(比B(S)大于B( R R R)时)更少一些

两趟算法

  大致思路:
  第一趟:对M块进行排序,形成子表(M-1)
  第二趟:对子表再进行排序

两阶段多路归并排序(TPMMS)

  TPMMS用如下方式对关系R进行排序:

  第1步:不断地将R中的元组放入M个缓冲区(M个块,每个块都有一定的元组),使用主存排序算法进行排序,并将排序得到的子表存入外存中(一次进入M块,在内存中对这M块进行排序,随后将排序结果写回到磁盘中)

  第2步:对排好序的子表进行归并,归并形式类似于二路归并,将归并的值复制到输出缓冲区。如果缓冲区(输出块)已满,则将输出块写入到硬盘,并重新初始化该缓冲块(假定子表数量N小于M,这一步首先是从N个子表中,各取出一个块放入到内存中,并且用一个缓冲区作为输出区。随后再对内存中的这N个块进行归并排序,将结果放入到输出区中。如果输出区满了,则直接输出并清空输出区;而如果用于读入某个子表的缓冲区读完了,则从那个子表中继续读入)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V3RJo6ji-1605536086174)(C:\Users\蔡三圈\AppData\Roaming\Typora\typora-user-images\image-20201116203750648.png)]

  因为读取了2次,写入了1次(每次大小都是B( R R R)),所以一共进行了3B( R R R)次磁盘I/O

  而子表不能超过M-1个(因为最后一个要用来当输出区)。现假设R占用B个块,则因为每个子表有M个块(不超过M个块),R就共占用了B/M个子表,满足约束: B / M ≤ M − 1 B/M\le M-1 B/MM1,即 B ≤ M ( M − 1 ) B\le M(M-1) BM(M1)

利用排序去除重复

  第一趟与多路归并完全一致,而第二趟则只输出第一次出现的元组t,而忽略其他几次重复出现的情形。其他方法保持不变,最后使用两趟算法计算 δ ( R ) \delta(R) δ(R)仅需要 B ( R ) \sqrt{B(R)} B(R) 个内存块

利用排序进行分组和聚集

  与前面不同的是,每一个子表都要使用一个主存缓冲区。在开始时,各子表按分组属性排序,在主存缓冲区中读入子表的第一个块。随后,反复从缓冲区可以获得的第一个元组中,找到分组属性的最小值,并为这个分组计算聚集

  此方法所需磁盘I/O次数也是3B( R R R)次,也是在 B ( R ) ≤ M ( M − 1 ) B(R)\le M(M-1) B(R)M(M1)时正常工作

基于排序的并算法

  只有集合才需要考虑两趟算法的情形(包可以只使用一趟算法就能完成)

  对于二元的并,自然是要读取两个关系。在两趟算法中,第一趟为创建关系R和S的排序子表(每个子表又设立一个内存缓冲区),而第二趟则是重复在所有缓冲区中查找剩余的第一个元组t,将t复制到输出,并从缓冲区中删除

  因为要对两个关系进行读取,所以磁盘I/O次数为3(B( R R R)+B(S)),而两关系的大小之和也应满足: B ( R ) + B ( S ) ≤ M ( M − 1 ) B(R)+B(S)\le M(M-1) B(R)+B(S)M(M1)

基于排序的交和差算法

  同理,只是更改了输出的条件

基于排序的一个简单的连接算法

  假定R和S的连接属性为Y

  1、以Y为排序关键字,对R和S进行两阶段多路归并排序
  2、重复执行下述步骤,以归并排好序的R和S(只使用两个缓冲区,分别遍历R和S):
  1) 在当前R和S的块的前端查找连接属性Y的最小值y
  2) 如果y在另一个关系的前部没有出现,则删除具有排序关键字y的元组
  3) 否则,找出两个关系中具有排序关键字y的所有元组
  4) 输出通过连接R和S中具有共同Y-值y的元组所能形成的所有元组

  这样的算法会使用5(B( R R R)+B(S))次磁盘I/O,且在B( R R R), B(S) ≤ \le M(M-1)的情况下正常工作

  一种改进的方式:多路归并时,对子表排好序输出后,可以先不把整个R和S排好序,而是直接读入各子表,然后开始连接(此方式假设重复元素并不多)。这样的算法只需要使用3(B( R R R)+B(S))次磁盘I/O

基于散列的两趟算法

  对于所有通常的操作,都有一种选择散列关键字的方法,它能使在我们执行该操作时需要一起考虑的所有元组分配到相同的桶

通过散列划分关系

  将块中的每个元组t散列到桶h(t)并复制到适当的缓冲区中,如果缓冲区满了则写入到磁盘(看着像是挤出去了一整块,然后自己进去了):

用M-1个空的缓冲区初始化M-1个桶;
FOR 关系R的每个块b DO BEGIN
    将块b读入第M个缓冲区中;
    FOR b中的每个元组t DO BEGIN
        IF 桶h(t)的缓冲区中没有容纳t的空间 THEN
            BEGIN
                将该缓冲区复制到磁盘;
                用一个新的空块初始化该缓冲区;
            END;
            将t复制到桶h(t)的缓冲区中;
    END;
END;
FOR 每个桶 DO
    IF 此桶的缓冲块非空 THEN
        将该缓冲区写到磁盘;

基于散列的消除重复算法

  因为在散列的过程中,相同元组t的两个副本会被散列在同一个桶中,因此可以一次检查一个桶,并在桶中独立执行 δ \delta δ

  散列时,R的每个块被读写各一次,而在针对各桶的算法中,又将R的每个块读取了一次。因此,磁盘I/O数为3B( R R R)

基于散列的分组和聚集算法

  为了确保同一分组的所有元组最终都在同一个桶内,选择的散列函数应该依赖于分组属性。其余同理(对各桶单独计算)

基于散列的并、交、差算法、散列连接算法

  对于二元的操作,需要同时散列两个关系R和S的元组。这样将会散列到2(M-1)个桶中,并在每个桶上使用一次相应的一趟算法

  而散列连接算法则需要用连接属性Y作为散列关键字

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值