MySQL的文档中提到了nested loop,block nested loop,index join,在8.0中新增了hash join。但是没有提到one-pass,tpmms
http://www.mathcs.emory.edu/~cheung/Courses/554/Syllabus/4-query-exec/
http://www.cs.sjsu.edu/faculty/pollett/157b.12.05s/Lec16032005.pdf
https://dev.mysql.com/doc/refman/8.0/en/hash-joins.html
表R和表S
B(R)和B(S)分别表示两个表的块数
可用内存大小为B(M)
最简单的算法
for (r in R) {
for (s in S) {
if (s,r) match condition, output (s,r)
}
}
IO消耗: B(R) * B(S)
内存限制:对内存要求不高,B(M) > = 2即可
如果是3个表,算法如下
for (r in R) {
for (s in S) {
for (t in T) {
if (r,s,t) match condition, output (r,s,t);
}
}
}
nested loop效率不高,因为没有将行数据缓存
block nested loop的思路是尽可能将一些数据缓存在内存,减少IO次数
do: load M-1 block data from R into M-1;
for (r in M-1) {
for (s in S) {
if (r,s) match condition, output (r,s)
}
}
repeat;
IO消耗:B(R) / B(M-1) * B(S) + B(R)
对内存要求不高: B(M) >= 3
如果是3个表,算法如下:
do: load data from R into M-1;
do: load from R into M-1
if (M-1 is full) {
for ( (r,s) in M-1) {
for (t in T) {
if (r,s,t) match condition, output (r,s,t);
}
}
empty(M-1)
}
}
repeat;
}
repeat;
// 上面的操作结束后,如果M-1里还有数据
for ( (r,s) in M-1) {
for (t in T) {
if (r,s,t) match condition, output (r,s,t);
}
}
如果其中一个表的数据量较小,比内存小,可以使用效率更高的one-pass算法
将较小的表全部放入内存,构造一个搜索结构(比如hash)
load all R into M-1, construct a hashMap "H"
for (s_ in S) {
search s_'s reference key in H, get r
if (r, s_) match condition, output (r, s_);
}
IO消耗(只考虑磁盘IO,内存操作可忽略):B(R) + B(S)。比block nested join好很多
内存限制:B(M) >= B(R) + 1,内存需要比R表大(假设R是R和S中较小的表)
R和S都较大,无法使用one-pass算法
phase 1:将R和S看成一段一段的,每段的大小都是M。依次将每段数据读入内存,排好序之后回写。
do: load M block data from R
sort these data, write back to R
repeat;
do: load M block data from S
sort these data, write back to S
repeat;
IO消耗:读写R,S各一次 => 2 *(B(R) + B(S))
内存限制:暂无
phase 2:
从R和S的每一段中,都取一块数据,放入内存(因此R和S的总段数不能大于内存块数)
循环: 从R和S中的每一段都取一个block,放入内存;M(R)表示内存中R表的数据,M(S)表示内存中S表的数据
从M(R)中取最小值r, M(S)中取最小值s;
如果r < s, 查找下一个r;
如果r > s, 查找下一个s;
r,s相等, 输出r,s的连接结果;
如果M(R)或M(S)中有一block全部查找过,则从该块来源的段中取下一块,替换该块。
循环:end
phase 2 分析:
IO消耗:读R,S = B(R) + B(S)
内存限制: R和S的总段数不能超过B(M) ,所以 B(R) + B(S) <= B(M)的平方
如果内存不足,连上面tpmms的内存要求都达不到,可以使用tpmms-先排序 算法。
伪代码如下:
phase1:
使用tpmms分别对R和S所有数据排序;
phase1 分析:
IO消耗:2次读,2次写 = 4(B(R) + B(S))
内存限制: B(R) <= B(M)的平方
phase2:
将R分成一段一段的,每段为M-1个块。
循环:从R中读取一段进内存;从S中读取一块进内存
循环:读取内存中R的记录r和内存中S的记录s
r < s, 读取下一个r;
r>s,去读下一个s;
r = s; 输出连接结果
循环:end
循环:end
IO消耗:B(R) + B(S)
内存限制: R中相同数量的r的最大数量不能超过B(M) - 1
总结:
IO消耗: 5 (B(R) + B(S))
伪代码:
phase1 :
将内存M的M-1块,每一块视为一个hash桶。设计一个总长度为B(M) - 1的hash算法。
分别对R和S做如下操作:
循环:读取R,S中的一块
读取块中每一条记录,执行hash计算,将记录放入计算后的内存hash桶;
如果桶满了,写回磁盘;
循环:end
将剩下没满的桶都写回磁盘。
phase1将R/S的数据组装成不连续的hash表。
phase1分析:
IO消耗:读,写R和S = 2(B (R) + B(S))
内存限制:无
phase 2:
在one-pass的连接运算中,首先将整个S放入内存构建hash,然后遍历R;
参考one-pass算法,每次从S中取一个桶的数据,放入内存
循环:从S中取一个桶的所有数据放入内存
用桶中每一条数据构造hash;
从R中取相应hash key的数据,每次读取一块到内存,与hash中的数据做连接操作
循环:end
phase 2分析:
IO消耗:B(R) + B(S)
内存限制:S或R中最大的桶的数据量不能超过B(M)-1条数据;
因此,内存绝对不能小于:在最理想的状态下,每个桶的大小相等都是 B(S) / ( B(M) - 1 ), 在其他情况下,最大桶的大小只会比这个值大,不会比这个值小。 所以内存绝对不能小于 ( B(M) - 1 )的平方;
当然,即使满足“绝对不能小于”的内存,也不代表一定定满足 “S或R中最大的桶的数据量不能超过B(M)-1条数据”;
假设 R 表 (x, y), S表(y, z), S表在y上有索引
for (r in R) {
search r.y in S, get List data s[];
for (s in s[]) {
if (r, s) match condition, output (r,s)
}
}
分析:
假设对于R中的每条记,S中等于与它对应的记录数有V条,则需要的IO是B(V);
总IO消耗为每条记录IO消耗的累加 = B(R) + T(R) * B(V)的平均值,T(R)表示T表的行数
理想情况:V等于1,则 IO消耗 = B(R) + T(R)
额外的IO:访问索引也可能需要IO,索引可以存在磁盘上(考虑索引是聚集的和非聚集的时候,IO消耗不同),这里暂且忽略
如果R的数据量较小,不管S的数据量有多大,index join的效率都很高