数据库查询实现

数据库的三大类操作

  • 一次单一元组的一元操作–select, projection
  • 整个关系的一元操作–distinct, group by, sort
  • 整个关系的二元操作:并,交,差,笛卡尔积,join
    对于一次单一元组的一元操作,有迭代器算法;对于整个关系的一元操作,有一趟扫描算法,二趟扫描算法,多趟扫描算法。扫描多次的主要原因是内存容量不够,这些扫描算法的实现都有基于排序、散列和索引的

1. 一次单一元组的一元操作

查询实现有两种策略,一种是物化计算策略,一种是流水线计算策略。其中物化策略的每一个操作都要扫描一遍数据库,且存储中间结果;流水线策略一组关系操作才扫描一遍数据库,不存储中间结果。流水线策略可用迭代器算法实现

迭代器的构造

表空间扫描法读取关系
假设R是一个类,以下是伪代码

Open(){
	b:=R的第一块
	t:=b的第一个元组
}
GetNext(){
	if(t已超过块b的最后一个元组){
		将b前进到下一块
		if(没有下一块)
			return notFound
		else/*b是一个新块*/
			t:=b的第一个元组;
	}
	oldt:=t;
	将t前进到b的下一个元组;
	return oldt;
}
Close(){
}

实现并操作
假设Union(R,S)是一个类

Open(){
	R.Open();
	CurRel:=R;//当前关系为R
}
GetNext(){
	if(CurRel==R){
		t:=R.GetNext();
		if(t<>notfound)
			return t;//未处理完
		else{//已处理完R
			S.Open();
			CurRel:=S;
		}
	}
	return S.GetNext();
}
Close(){
	R.close();
	S.close();
}

实现selection操作
Selection(R)

Open(){
   R.Open();
}
GetNext(){
   cont://一个条件循环
   	t:=R.GetNext();
   	if(t<>notfound)
   		if(F(t)==True)//条件判断
   			return t;
   		else goto cont;
   	else return notfound;
}
Close(){
   R.Close();
}

在selection操作的基础上做projection操作
Projection(Selection(R))

Open(){
   Selection.Open();
}
GetNext(){
   t:=Selection.GetNext();
   if(t<>notfound){
   	p:=Projection(t,alpha)//alpha为投影内容
   	return p
   }
   else return notfound;
}
Close(){
   Selection.Close();
}

这样就可以自动地从前一个操作的迭代器构造后一个操作的迭代器
迭代器实现Join
Join(R,S)

Open(){
   R.Open();
   S.Open();
   r:=R.GetNext();
}
GetNext(){
   repeat{
   	s:=s.GetNext();
   	if(s==notfound){
   		S.Close();
   		r:=R.GetNext();
   		if(r==notfound)
   			return notfound;
   		else{
   			S.Open();
   			s:=S.GetNext();
   		}
   	}
   }
   until(r与s能够连接)
   return r和s的连接
}
Close(){
   R.Close();
   S.Close();
}

2. 整个关系的一元操作

对于关系R,假设B(R)是R的存储块数目;T(R)是R的元组数目

一趟扫描:

  • 对于聚簇关系——关系的元组集中存放(一个块中仅是一个关系中的元组):
    表空间扫描算法:
    TableScan(R)–扫描结果未排序:B(R)
    SortTableScan(R)–扫描结果排序:3B(R)
    索引扫描算法:
    IndexScan(R)–扫描结果未排序:B(R)
    SortIndexScan(R)–扫描结果排序:B(R) or 3B(R)
  • 对于非聚簇关系——关系元组不一定集中存放(一个块中不仅是一个关系中的元组):
    扫描结果未排序:T(R)
    扫描结果排序:T(R)+2B(R)(可以理解为排序后变为聚簇的)

去重复操作:&(R)

  • 需要在内存中保存已处理过的元组
  • 当新元组到达时,需与之前处理过的元组进行比较
  • 建立不同的内存数据结构(比如散列表,B+树),来保存之前处理过的数据,以便快速处理整个关系上的操作(用于快速定位元组)
    算法复杂度为B(R)
    两趟扫描的去重复操作:复杂度和两趟扫描排序相同,考虑输出是 4 B p r o b l e m 4B_{problem} 4Bproblem,不考虑输出是 3 B p r o b l e m 3B_{problem} 3Bproblem

分组聚集 γ L ( R ) \gamma_{L}(R) γL(R) γ \gamma γ为分组计算符号,L为分组条件

  • 需要在内存中保存所有的分组
  • 保存每个分组上的聚集信息
  • 建立不同的内存数据结构(比如散列表,B+树),来保存之前处理过的数据,以便快速处理整个关系上的操作(用于快速定位元组)
    类似去重复操作,算法复杂度为B(R)
    比如使用散列,则散列函数可以是分组条件的函数;新分组通过散列插入相应的桶(页)中;新元组通过散列找到相应的桶,并判断是否是新分组
    两趟扫描:第一趟分组并子表排序;第二趟,归并阶段,在排序的基础上,将不重复的记录作为新分组输出,将重复的记录进行分组聚集计算。复杂度和两趟扫描排序相同,考虑输出是 4 B p r o b l e m 4B_{problem} 4Bproblem,不考虑输出是 3 B p r o b l e m 3B_{problem} 3Bproblem

两趟扫描

假设内存只有8块,如何排序70块的数据集?
在这里插入图片描述
问题是在全部数据上的操作是否等价于在子集上的操作的并集?
例如:元组在某一子集上无重复即在全集上无重复。
基于散列的两趟扫描算法可以满足这一条件

  • 基于散列的两趟扫描算法
    大数据集上的操作可以被转换为某个子集上的操作:
    第一趟:散列子表。用散列函数 h p h_{p} hp将原始关系划分成M-1个子表,并存储。
    第二趟:处理每个子表。用另一散列函数 h r h_{r} hr将子表读入内存并建立内存结构。复杂性与上述两趟扫描的复杂性相同
    对于去重复操作:元组在子表上不重复,则在大关系中亦不重复。 H p H_{p} Hp将可能重复的元组散列到同一子表, h r h_{r} hr将可能重复的元组散列到同一内存块中。
    对于分组操作:第一趟散列时应该同一个分组在同一子表中;同样第二趟散列时同一个分组应散列到同一个内存块中。两趟散列都应该针对“分组属性”的值进行计算。但可以形成区别,如 H p H_{p} Hp直接计算分组属性, h r h_{r} hr用“分组属性”的二进制位串。复杂性与上述两趟扫描的相同。

对于基于排序的两趟扫描算法可以满足 在多个已按横向处理的子集上,纵向归并结果等同于在全集上的处理结果。

  • 两阶段多路归并排序(two-phase, multiway merge-sort, TPMMS)
    • 内排序问题和外排序问题的区别:内排序问题–待排序的数据可一次性地装入内存中,即排序这可以完整地看到和操作所有数据。 外排序问题–所有的数据不能一次性装入内存
    • 基本排序策略。假设所有的数据需要磁盘块 B p r o b l e m B_{problem} Bproblem块,内存有 B m e m o r y B_{memory} Bmemory块, B p r o b l e m B_{problem} Bproblem远大于 B m e m o r y B_{memory} Bmemory。则可把 B p r o b l e m B_{problem} Bproblem块数据划分为N个子集合,使得每个子集合的块数小于内存可用的块数,即 B p r o b l e m / N < B m e m o r y B_{problem}/N<B_{memory} Bproblem/N<Bmemory。每个子集合都可装入内存并采用内排序算法排好并重新写回磁盘,这一过程为第一趟排序,也可以叫横向处理。于是问题转化为:N个已排序子集合的数据怎样利用内存进行总排序,这一步称为第二趟排序,是各子集间的归并排序,每个子集都被读取一部分内容放入内存中
      在这里插入图片描述
      算法的效率:子集合排序阶段读一遍写一遍,代价为 2 B p r o b l e m 2B_{problem} 2Bproblem,归并阶段读一遍写一遍,代价为 2 B p r o b l e m 2B_{problem} 2Bproblem,总共为 4 B p r o b l e m 4B_{problem} 4Bproblem.要求是大数据集块数< B m e m o r y 2 B_{memory}^2 Bmemory2
      更大规模的数据集则考虑一多趟/多阶段排序。假设内存大小 B m e m o r y = 3 B_{memory}=3 Bmemory=3,待排序数据 B p r o b l e m = 30 B_{problem}=30 Bproblem=30
      基本策略:
      1.将30块的数据集划分为10个子集合,每个子集合3块,排序并存储。
      2.将10个已排序的子集合分为5组,每组两个子集合,分别进行二路归并,则可得到5个排好序的集合
      3.5个集合再分成3个组:每个组两个子集合,剩余一个单独一组,分别进行二路归并,可得到3个排好序的集合;再分组再归并得到两个排好序的集合;再分组再归并便可完成最终的排序

3.整个关系的二元操作

一趟扫描

先扫描一个关系,再去扫描另一个关系。在集合上的操作(需要去重复)和在包上的操作(需要记录每个元组出现的次数)有所不同。
算法复杂度:B(R)+B(S)

基于有序索引的连接算法–zig-zag连接算法
在这里插入图片描述

两趟扫描

包上的操作都无需两趟,直接合并即可。集合上的操作需要两趟。
基于散列的两趟扫描

  • 第一趟:使用相同的散列函数散列两个操作对象,如R和S,形成子表 R i R_{i} Ri S i S_{i} Si
  • 第二趟:将 S i S_{i} Si再整体散列读入到内存中,再依次处理 R i R_{i} Ri的每一块。如判断在 R i R_{i} Ri S i S_{i} Si都出现元组t,则只输出t的一个副本,否则输出 R i R_{i} Ri S i S_{i} Si

连接操作:
基于散列的两趟扫描十分适合连接操作,

  • 第一趟:使用相同散列函数(散列的属性为连接属性)散列两个操作对象R和S
  • 第二趟:将 S i S_{i} Si再整体散列读入到内存中,再依次处理 R i R_{i} Ri的每一块。进行连接。(散列到相同位置的 R i R_{i} Ri S i S_{i} Si具有相同的连接属性值)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值