数据库-连接运算

本博客会陆续写一些和操作数据有关的基本算法。内容都很基础,算是帮助大家回顾记忆。也可以给和我一样,刚接触数据库,数据挖掘等技术的同学,提供一个迅速了解基本算法的文档。我认为多多体会基本算法,不光是为了编程、性能优化,还可以学习到很多分析解决问题的方法。好了,不多废话,欢迎大家来评论;如文中有错误,也欢迎大家来拍砖哈~

我们查询数据时经常会用到联合查询

select r,s from R join S on R.id=S.rid

这个连接是怎么运算的呢?代价有多大呢?我们来分析一下。先来看下运算结果集的大小:若R∩S=空,则连接为笛卡尔积。 若R∩S是R的码,可知s的一个元组至多与r的一个元组连接。因此连接结果集的元组数不会超过s中的元组数。 若R∩S既不是R的码也不是S的码,令R∩S={A},设S的元组数ns,V(A,s)为S中属性A所具有的不同值的数目,则ns/V(A,s)是S关系中属性A的值给定情况下平均的元组数目,则在r连接s的结果集中有nr*ns/V(A,s)个元组。

首先介绍最基础的连接方式:嵌套循环连接。要计算r和s的theta连接,看伪代码:

for each 元组tr in r do
begin
for each 元组ts in s do
begin
测试元组对(tr,ts)是否满足连接条件theta
如果满足,把tr*ts加到结果中
end
end

若元组对数目是nr*ns,对于关系r中的每一条记录,我们必须对s做一次完整扫描。最好的情况,内存空间容纳两个关系,此时每一数据块只需赌一次,从而只需bs+br次块存取(设bs,br代表含有关系S,R的元组块数目)。如果较小的那个关系能完全放在内存中,把这个关系作为内层关系来处理,则内层循环关系只需读一次。最坏的情况下,缓冲区只能容纳每个关系的一个数据块,这是共需nr*bs+br次块存取。

可以看到,当内存不能同时容纳两个关系,这种方法的代价很高。下面我们做一下小的优化:

for each 块 Br of r do
begin
for each 块 Bs of s do
begin
for each 元组 tr in Br do
begin
for each 元组ts in s do
begin
测试元组对(tr,ts)是否满足连接条件θ
如果满足,把tr*ts加到结果中
end
end
end
end
此算法以块的方式而不是以元组的方式处理关系,叫做块嵌套循环连接。最坏的情况下,对于外层关系的每一块,内层关系s的每一块只需读一次,需br*bs+br次块访问。在块嵌套循环连接算法中,外层关系的块可以不用磁盘块作单位,而以内存中最多能容纳的大小为单位。若内层循环连接属性上有索引,可以用更有效的索引查找法代替文件扫描法。

上面的两种算法可以用于所有连接运算:(如果是自然连接或等值连接中的连接属性是内层关系的码,则内层循环一旦找到首条匹配元组就可以终止。)。如果是自然连接或等值连接,还有更高效的算法:

归并连接

pr = r的第一个元组的地址;
ps = s的第一个元组的地址;
while(ps不等于null and pr不等于null) do
begin
ts=ps所指向的元组;
Ss={ts};
让ps指向关系s的下一个元组;
done=false;
while(not done and ps不等于null)do
begin
ts=ps所指向的元组;
if(ts[JoinAttrs]=tr[JoinAttrs])
then begin
Ss=Ss并ts;
让ps指向关系s的下一个元组;
end
else done=true;
end
tr=pr所指向的元组;
while(pr不等于null and tr[JoinAttrs]<ts[JoinAttrs])do
begin
让pr指向关系r的下一个元组;
tr=pr所指向的元组;
end
while(pr不等于null and tr[JoinAttrs]=ts[JoinAttrs])do
begin
for each ts in Ss do
begin
将ts连接tr加入结果中;
end
让pr指向关系r的下一个元组;
tr=pr所指向的元组;
end
end

归并连接算法为每个关系分配一个指针,这些指针一开始指向对应关系的第一个元组,随着算法的进行,指针遍历整个关系。其中一个关系中在连接属性上有相同值的所有元组被加入到Ss中。由于关系已排序,这样已排序的每一元组只需读一次,磁盘访问次数:br+bs。

散列连接

如果关系r的一个元组与关系s的一个元组满足连接条件,那么它们在连接属性上有相同的值。若该值经散列函数映射为i,则关系r的那个元组必在Hri中,关系s的那个元组必在Hsi中。因此,Hri中的元组只需与Hsi中的元组想比较,而无需与s的其它任何分划比较。

for each 元组 ts in s do
begin Hsi=h(ts[JoinAttrs]);
Hsi=Hsi并{ts};
end
for each 元组 tr in r do
begin Hri=h(tr[JoinAttrs]);
Hri=Hri并{tr};
end
for Hsi=0 to max do
begin 读Hsi在内存中建立其散列索引;
for each 元组tr in Hsi do
begin 检索Hsi的散列索引,定位所有满足tr[JoinAttrs]=ts

[JoinAttrs]的元组ts;
for each 匹配的元组ts in Hsi do
begin 将tr连接ts加入结果中;
end
end
end

选择的max值应足够大,以使对任意i,内存中可以容纳构造用输入关系的任意Hsi以及其上的散列索引。最好用较小的输入关系作为构造用输入关系。

递归划分:如果max的值大于等于内存页帧数,关系的划分不可能一趟完成。每一趟中,输入的可最多分裂的分化数不超过用于输出的缓冲页数。每一趟产生的存储桶又在下一趟中分别读入并 再次划分。每趟划分中所用的散列函数与上一趟所用的散列函数是不同的。分裂过程不断重复直到构造用输入关系的各个分划都能被内存容纳为止。当M(内存块)>max+1,max=b/M,关系不需要递归划分。

溢出处理:当Hsi的散列索引大于内存时,构造用输入关系s的分划i发生散列表溢出。原因:相同值元组多;散列函数没有随机性、均匀性。
分解:任给i,若发现Hsi太大,用另一个散列函数进一步将之划分。
避让:将关系s划分成许多小分划,然后把某些分划组合在一起,确保组合后的分划

能被内存容纳。
如果有大象相同值,不采用在内存中创建散列索引后用嵌套循环连接算法对分划连接的方法,而是在分划使用其他技术,如块嵌套循环连接。

代价:
无递归划分:3(br+bs)+2*max。分划时读入写出2(br+bs),构造检索每个分划读入br+bs,max分划中每个分划可能有一个部分满的块,读写各一次,不超过2*max。
有递归划分:2(br+bs)[logM-1(bs)-1]+br+bs。每一趟分划大小减小为原来的1/(M-1),直到每个分划最多占M块。

复杂连接

嵌套循环连接于块嵌套循环连接不管在什么条件下均可使用。其他连接技术比嵌套循环连接更有效,但只能处理简单的连接条件,如自然连接或等值连接。
例如:
r与s在(theta1∩theta2∩…thetan)条件下的连接,可分别计算r与s在thetan下的连接,在计算其满足(theta1∩theta2∩…thetan)条件的连接。
3个关系的连接除了利用"结合律",还可以两边建索引,计算中间每一个元组与两边对应的元组。

你可能感兴趣的:(数据库)