PG归并排序算法详解
前言
归并排序算法是连接算法中比较复杂的算法,相比嵌套循环与Hash匹配而言。本节会通过实例来说明该算法在PG中的具体实现。
在PG中,通过状态机来实现——归并-连接。当然这里的完整流程是排序——归并——连接,由于排序通过Sort操作来完成,这里就不赘述。
这里的状态机一共有11中状态,在这11中状态的转换过程中,会根据外表(inner)或内表(inner)读取到数据的状态分为——
MJEVAL_MATCHABLE、MJEVAL_NONMATCHABLE、MJEVAL_ENDOFJOIN。可以简单理解为读取到非空数据,读取到数据为空以及所有数据读取完成。前置条件,内外表均已排序完成且都是升序策略。
-----------初始状态-----------------
EXEC_MJ_INITIALIZE_OUTER
EXEC_MJ_INITIALIZE_INNER
-----------中间状态-----------------
EXEC_MJ_NEXTOUTER
EXEC_MJ_TESTOUTER
EXEC_MJ_NEXTINNER
EXEC_MJ_SKIP_TEST
EXEC_MJ_SKIPOUTER_ADVANCE
EXEC_MJ_SKIPINNER_ADVANCE
-------------结束状态-----------------
EXEC_MJ_JOINTUPLES
EXEC_MJ_ENDOUTER
EXEC_MJ_ENDINNER
举例
- 接下来,以一个实例来说明状态机转换的具体流程。这里假设有两张表进行全外连接——outer/inner。数据如下所示:
outer inner
5 5
5 5
6 8
6 8
7 12
8 14
简单提及一下后面说明过程中会涉及到的变量
1 (current) outer tuple扫描到的一条外表数据,eg: 5/6/7
2 (current) inner tuple扫描到的一条内表数据,eg: 8/12/14
3 marked tuple 上一次与outer tuple匹配到的inner tuple,记录为marked tuple(描述的是inner tuple)
4 offset tuple 在方法ExecMarkPos执行时,会将当前inner tuple的位置做标记,以便后面inner tuple的重新扫描。通过方法ExecSortRestrPos实现标记。通过方法ExecRestrPos来实现将(current) inner tuple恢复为offset tuple。
1 第一轮,初始状态为EXEC_MJ_INITIALIZE_OUTER,外表游标下移获取到outer tuple——5(1)。通过MJEVAL_MATCHABLE切换到状态EXEC_MJ_INITIALIZE_INNER。
在状态EXEC_MJ_INITIALIZE_INNER下,内表游标下移获取到——5(1)。通过MJEVAL_MATCHABLE切换到EXEC_MJ_SKIP_TEST。
来到状态EXEC_MJ_SKIP_TEST,这里执行方法MJCompare,也就是对outer tuple与inner tuple进行了比较,5(1) == 5(1)。标记offset为current,同时将marked tuple置为当前tuple。然后切换到状态EXEC_MJ_JOINTUPLES。
在状态EXEC_MJ_JOINTUPLES下,首先更改状态为EXEC_MJ_NEXTINNER,然后将匹配到的数据返回。
到这里为止,完成了外表/内表的数据匹配,一轮匹配就结束了。
匹配结果 5(1) == 5(1)
outer inner
outer tuple - 5 5 - marked tuple - offset tuple - inner tuple
5 5
6 8
6 8
7 12
8 14
2 第二轮,由于上一轮在退出时将状态置为EXEC_MJ_NEXTINNER,因此,这一轮从状态EXEC_MJ_NEXTINNER出发。该状态下,内表游标下移获取数据——5(2),然后调用方法MJCompare比较outer tuple与inner tuple,5(1) == 5(2),切换到状态EXEC_MJ_JOINTUPLES。同样,切换状态为EXEC_MJ_NEXTINNER,并且完成该轮扫描(后面不再赘述)。
匹配结果 5(1) == 5(2)
outer inner
outer tuple - 5 5 - marked tuple - offset tuple
5 5 - inner tuple
6 8
6 8
7 12
8 14
3 第三轮,继续来到状态EXEC_MJ_NEXTINNER,此时内表游标下移获取数据——8(1),与outer tuple相比——5(1) < 8(1),切换到状态EXEC_MJ_NEXTOUTER。
在状态EXEC_MJ_NEXTOUTER下,外表游标下移获取到outer tuple——5(2),同时通过MJEVAL_MATCHABLE切换到EXEC_MJ_TESTOUTER。
在状态EXEC_MJ_TESTOUTER下,这里对于outer tuple与marked tuple进行了比较,也就是5(2) == 5(1),这里调用方法ExecRestrPos重置了内表的游标,将inner tuple置为offset tuple,也就是说,下一次内表游标的下移后获取的数据是——5(2)。然后切换状态为EXEC_MJ_JOINTUPLES,同时结束了本轮的匹配。
匹配结果 5(2) == 5(1)
outer inner
5 5 - marked tuple - offset tuple - inner tuple
outer tuple - 5 5
6 8
6 8
7 12
8 14
4 第四轮,其实与第二轮的描述基本一致,只是结果发生了变化
匹配结果 5(2) == 5(2)
outer inner
5 5 - marked tuple - offset tuple
outer tuple - 5 5 - inner tuple
6 8
6 8
7 12
8 14
5 第五轮,在状态EXEC_MJ_NEXTINNER下,内表游标下移获取数据——8(1),需要注意的是,这里将mj_MatchedInner标记为false。与outer tuple比较——5(2) < 8(1),切换到状态EXEC_MJ_NEXTOUTER。
在状态EXEC_MJ_NEXTOUTER下,外表游标下移获取到outer tuple——6(1),同时通过MJEVAL_MATCHABLE切换到EXEC_MJ_TESTOUTER。
在状态EXEC_MJ_TESTOUTER下,这里对于outer tuple与marked tuple进行了比较——6(1) > 5(1),重新载入current inner tuple——8(1),然后切换到状态EXEC_MJ_SKIP_TEST。
在状态EXEC_MJ_SKIP_TEST下,调用方法MJCompare比较outer tuple与inner tuple——6(1) < 8(1)。切换到状态EXEC_MJ_SKIPOUTER_ADVANCE。
来到状态EXEC_MJ_SKIPOUTER_ADVANCE。由于在该轮开始,变量mj_MatchedOuter已经被置为false,因此,这里并没有继续外表的游标下移,而是修改变量mj_MatchedOuter为true,然后调用MJFillOuter将其关联的inner tuple置为空。这里没有进行状态的切换。该轮循环结束。
关联结果 6(1) null
outer inner
5 5 - marked tuple - offset tuple
5 5
outer tuple - 6 8 - inner tuple
6 8
7 12
8 14
6 由于上面一轮并没有发生状态的切换,因此,该轮继续从状态EXEC_MJ_SKIPOUTER_ADVANCE出发,只是,上一轮结束时,将变量mj_MatchedOuter置为true,因此,外表游标下移获取到outer tuple——6(2),将mj_MatchedOuter置为false,然后切换到状态EXEC_MJ_SKIP_TEST。
来到状态EXEC_MJ_SKIP_TEST,调用方法MJCompare比较outer tuple与inner tuple——6(2) < 8(1)。切换到状态EXEC_MJ_SKIPOUTER_ADVANCE。与上一轮相似,这里同样调用MJFillOuter将其关联的inner tuple置为空。这里没有进行状态的切换。该轮循环结束。
关联结果 6(2) null
outer inner
5 5 - marked tuple - offset tuple
5 5
6 8 - inner tuple
outer tuple - 6 8
7 12
8 14
7 这一轮的流程基本与第六轮相似,只是外表游标下移获取到outer tuple——7。
关联结果 7 null
outer inner
5 5 - marked tuple - offset tuple
5 5
6 8 - inner tuple
6 8
outer tuple - 7 12
8 14
8 该轮同样从状态EXEC_MJ_SKIPOUTER_ADVANCE出发,外表游标下移获取到outer tuple——8,状态切换到EXEC_MJ_SKIP_TEST。
来到状态EXEC_MJ_SKIP_TEST,调用方法MJCompare比较outer tuple与inner tuple——8 == 8(1)。这里标记offset为current,同时将marked tuple置为当前tuple。然后切换到状态EXEC_MJ_JOINTUPLES。同时结束了本轮的匹配。(不要忘记,下一轮从状态EXEC_MJ_NEXTINNER出发)
关联结果 8 == 8(1)
outer inner
5 5
5 5
6 8 - marked tuple - offset tuple - inner tuple
6 8
7 12
outer tuple - 8 14
9 该轮从状态EXEC_MJ_NEXTINNER出发,内表游标下移获取数据——8(2),调用方法MJCompare与outer tuple相比较——8 == 8(2),因此,切换到状态EXEC_MJ_JOINTUPLES,同时结束本轮的匹配。
关联结果 8 == 8(2)
outer inner
5 5
5 5
6 8 - marked tuple - offset tuple
6 8 - inner tuple
7 12
outer tuple - 8 14
10 该轮同样从状态EXEC_MJ_NEXTINNER出发,内表游标下移获取数据——8(2),同时将变量mj_MatchedInner设置为false,调用方法MJCompare与outer tuple相比较——8 < 12,故切换到状态EXEC_MJ_NEXTOUTER。
在状态EXEC_MJ_NEXTOUTER下,外表游标下移,同时将变量mj_MatchedOuter置为false,由于此时外表已经没有数据了,因此通过MJEVAL_ENDOFJOIN切换到状态EXEC_MJ_ENDOUTER。
来到状态EXEC_MJ_ENDOUTER,由于变量mj_MatchedInner为false,这里重置mj_MatchedInner为true后,调用方法MJFillInner,将inner tuple——12关联的outer tuple填充为null。这里没有状态切换。该轮循环结束。
关联结果 null 12
outer inner
5 5
5 5
6 8 - marked tuple - offset tuple
6 8
7 12 - inner tuple
8 14
outer tuple - eof eof
11 该轮从上一轮结束的状态EXEC_MJ_ENDOUTER出发。由于mj_MatchedInner为true,首先调用方法ExecMarkPos标记offset为current,然后内表游标下移,同时将变量变量mj_MatchedInner设置为false,状态不变。
继续来到状态,这里与继续调用方法MJFillInner,将inner tuple——14关联的outer tuple填充为null。这里没有状态切换。该轮循环结束。
关联结果 null 14
outer inner
5 5
5 5
6 8 - marked tuple
6 8
7 12 - offset tuple
8 14 - inner tuple
outer tuple - eof eof
12 仍然是状态EXEC_MJ_ENDOUTER,这里同样首先调用方法ExecMarkPos标记offset为current,然后内表游标下移,同时将变量变量mj_MatchedInner设置为false。由于此时内表的扫描结束,返回为空,同时该轮循环结束。
关联结果 null
outer inner
5 5
5 5
6 8 - marked tuple
6 8
7 12
8 14 - offset tuple
outer tuple - eof eof - inner tuple
- 至此,归并排序的举例就结束了。我推荐大家在阅读本文的时候最好参照pg源码——nodeMergejoin.c文件中的ExecMergeJoin方法,有事半功倍之效。
总结
状态EXEC_MJ_NEXTINNER,该状态的前置状态只有EXEC_MJ_JOINTUPLES。在该状态下内表游标下移获取数据,因此,outer tuple与inner tuple比较只有两种情况
- 1 outer tuple = inner tuple
- 2 outer tuple < inner tuple
状态EXEC_MJ_TESTOUTER,该状态的前置状态只有EXEC_MJ_NEXTINNER与EXEC_MJ_NEXTOUTER。而在该状态下外表游标下移获取数据,因此,outer tuple与inner tuple比较只有两种情况
- 1 outer tuple = inner tuple
- 2 outer tuple > inner tuple
状态EXEC_MJ_NEXTOUTER与EXEC_MJ_SKIPOUTER_ADVANCE的区别:
前者会切换到状态EXEC_MJ_TESTOUTER来比较。
而后者会切换到EXEC_MJ_SKIP_TEST来比较。
状态EXEC_MJ_SKIP_TEST与EXEC_MJ_TESTOUTER区别:
前者outer tuple与inner tuple比较有三种可能的结果。这里比较的是current outer tuple与current inner tuple。
而后者只有两种可能的情况。而这里比较的是current outer tuple与makred inner tuple。
如果current outer tuple > marked inner tuple,那么inner tuple从current tuple与outer tuple切换到状态EXEC_MJ_SKIP_TEST进行比较。
恢复标记
在状态EXEC_MJ_TESTOUTER中遇到outer tuple == marked tuple时,这里会置inner tuple为offset tuple,并且将inner tuple置为之前保存的offset tuple。
图示

PG归并排序算法详解的更多相关文章
- 八大排序算法详解(动图演示 思路分析 实例代码java 复杂度分析 适用场景)
一.分类 1.内部排序和外部排序 内部排序:待排序记录存放在计算机随机存储器中(说简单点,就是内存)进行的排序过程. 外部排序:待排序记录的数量很大,以致于内存不能一次容纳全部记录,所以在排序过程中需 ...
- BM算法 Boyer-Moore高质量实现代码详解与算法详解
Boyer-Moore高质量实现代码详解与算法详解 鉴于我见到对算法本身分析非常透彻的文章以及实现的非常精巧的文章,所以就转载了,本文的贡献在于将两者结合起来,方便大家了解代码实现! 算法详解转自:h ...
- kmp算法详解
转自:http://blog.csdn.net/ddupd/article/details/19899263 KMP算法详解 KMP算法简介: KMP算法是一种高效的字符串匹配算法,关于字符串匹配最简 ...
- 机器学习经典算法详解及Python实现--基于SMO的SVM分类器
原文:http://blog.csdn.net/suipingsp/article/details/41645779 支持向量机基本上是最好的有监督学习算法,因其英文名为support vector ...
- [转] KMP算法详解
转载自:http://www.matrix67.com/blog/archives/115 KMP算法详解 如果机房马上要关门了,或者你急着要和MM约会,请直接跳到第六个自然段. 我们这里说的K ...
- 【转】AC算法详解
原文转自:http://blog.csdn.net/joylnwang/article/details/6793192 AC算法是Alfred V.Aho(<编译原理>(龙书)的作者),和 ...
- KMP算法详解(转自中学生OI写的。。ORZ!)
KMP算法详解 如果机房马上要关门了,或者你急着要和MM约会,请直接跳到第六个自然段. 我们这里说的KMP不是拿来放电影的(虽然我很喜欢这个软件),而是一种算法.KMP算法是拿来处理字符串匹配的.换句 ...
- EM算法详解
EM算法详解 1 极大似然估计 假设有如图1的X所示的抽取的n个学生某门课程的成绩,又知学生的成绩符合高斯分布f(x|μ,σ2),求学生的成绩最符合哪种高斯分布,即μ和σ2最优值是什么? 图1 学生成 ...
- Tarjan算法详解
Tarjan算法详解 今天偶然发现了这个算法,看了好久,终于明白了一些表层的知识....在这里和大家分享一下... Tarjan算法是一个求解极大强联通子图的算法,相信这些东西大家都在网络上百度过了, ...
随机推荐
- JavaEE基础:过滤器、监听器、拦截器,应用...
写在前面说起Java和C++,很容易想到让人疯狂的指针,Java使用了内存动态分配和垃圾回收技术,让我们从C++的各种指针问题中摆脱出来,更加专心于业务逻辑,不过如果我们需要深入了解java的JVM相 ...
- Redmine it!
redmine插件开发简介 最稳妥的学习应该是先看官方文档,官方还给了一个具体的插件开发教程,不过如果一步不差按照教程敲代码,其实会发现还是有些问题的,需要稍稍改动. 这里,我自己编写了一个简单的插件 ...
- DOCKER中centos7的中文支持
直接编写看下能否改变成识别中文字体 写到你的~/.bashrc里吧,然后重启终端(我写的是英文的啊,改成你要的) export LC_ALL=en_US.UTF-8 export LANGUAGE=e ...
- IP地址结构分类(包括主机号和网络好计算)
IP地址:互联网上的每个接口的唯一标识. 长度:32bit. 五类不同的互联网地址格式: 各类IP地址范围: 点分十进制:32位的地址通常写成四个十进制数,其中每个整数对应一个字节. 主机号和网络的计 ...
- hibernate主键(generator)生成方式
1) assigned 主键由外部程序负责生成,无需Hibernate参与. 2) hilo 通过hi/lo 算法实现的主键生成机制,需要额外的数据库表保存主键生成历史状态. 3) seqhilo 与 ...
- <USACO06FEB>奶牛零食Treats for the Cowsの思路
写不来dp的日常 ....就这样吧 #include<cstdio> #include<cstring> #include<iostream> #include&l ...
- liubo.im
elite-lessons (37) --> 1025 精英水平的道歉 29/10/2016 1027 听大脑说话.给大脑编程 27/10/2016 1022 美国两党政治为什么越来越极化? 2 ...
- python-pymysql学习
python 操作mysql:有两种方式python-mysqldb(python3.0之后不能安装)和pymysql,下面是pymysql的学习. 参照表: python代码实现的mysql查询功能 ...
- Substring(Codeforces-D-拓扑排序)
D. Substring time limit per test 3 seconds memory limit per test 256 megabytes You are given a graph ...
- frp端口映射穿透内网
前言 frp 是一个高性能的反向代理应用,可以轻松地进行内网穿透,对外网提供服务,支持 TCP.UDP.HTTP.HTTPS 等协议类型,并且 web 服务支持根据域名进行路由转发. Github: ...