小明发现到大明家里问问题不但能收获知识,还能收获大腰子和红酒,于是更加坚定了常来问问题的决心。
小明和大明吃完了午饭,打开电视看一会电视剧,大明又惬意的惬意的抽起了中华烟,小明闻着二手烟有点心烦,就问大明:“哥,你整天抽抽抽,有那么好抽吗?”大明自豪的说:“咋地,咱爷爷抽烟,咱爸爸也抽烟,到我这不能断了香火,你不抽烟是不孝知道不?”
小明说:“咱爷爷抽烟是在农村种的烟叶,自给自足,还省钱,你也干脆回农村种烟叶,你这中华烟和农村的自己卷的烟叶,能有什么区别?”
大明看电视剧正看得起劲,心不在焉的说:“自己种的烟叶直接用报纸卷了抽,没有过滤嘴,会吸入有害颗粒物,而且烟叶的味道也不如现在改进的香烟。”说到这里大明好像想到了些什么,继续说:“这就像是查询优化器的逻辑优化,查询树输入之后,需要进行持续的改进,无论是自己用报纸卷的烟,还是在超市买的成品烟,它都是香烟,但是通过改进之后,香烟的毒害作用更低、香型更丰富了,逻辑优化也是这个道理,通过改进查询树,能够得到一个更‘好’的查询树。”
“哦,那逻辑优化是如何在已有的查询树上增加香型的呢?”
大明感觉电视剧没办法看下去了,于是拿起遥控器关了电视,继续说:“我总结,PostgreSQL在逻辑优化阶段有这么几个重要的优化:子查询&子连接提升、表达式预处理、外连接消除、谓词下推、连接顺序交换、等价类推理。”大明又抽了一口烟,接着说到:“从代码逻辑上来看,我们还可以把子查询&子连接提升、表达式预处理、外连接消除叫做逻辑重写优化,因为他们主要是对查询树进行改造,而后面的谓词下推、连接顺序交换、等价类推理则可以称之为逻辑分解优化,他们已经把查询树蹂躏的不成样子了,已经到了看香烟不是香烟的地步了。”
“可是我们的数据库原理课上并没有说有逻辑重写优化和逻辑分解优化啊?”
“嗯,是的,这是我自己根据PostgreSQL的源代码的特征自己总结的,不过它能比较形象的将现有的逻辑优化区分开来,这样就能更好的对逻辑优化进行提炼、总结、分析。”大明想了一下觉得如果把所有的逻辑优化的规则都说完有点多,决定选择其中的一两个说一下,于是对小明说道:“我们就从中挑选一两个详细的说明一下吧,我们先从子查询&子连接的提升开始说起。”
“那。。。子查询和子连接有什么区别呢?我们在数据库原理课里只讲了子查询,没有子连接的概念,这该怎么解释呢?”小明不解的问。
大明去书柜里拿出了《数据库系统实现》这本书,然后翻开了对应的章节,说道:“通常数据库原理书籍中说的子查询,指的是PostgreSQL数据库中的子连接。你看,《数据库系统实现》中说的是从条件中去除子查询,但是PostgreSQL把这种情况归类为子连接。”
“那在PostgreSQL是如何区分子查询和子连接的呢?”大明自问自答道:“在实际应用中可以通过子句所处的位置来区分子连接和子查询,出现在FROM关键字后的子句是子查询语句,出现在WHERE/ON等约束条件中或投影中的子句是子连接语句。”说着大明快速的在电脑上打开了记事本,敲入了几个SQL语句:
1 | 1. SELECT * FROM STUDENT, (SELECT * FROM SCORE) as sc; |
“这些SQL语句中哪个是子查询?哪个是子连接?”
小明看了一下,然后说:“1是子查询,2和3是子连接,语句1里面的子句出现在FROM后面,它是以‘表’的形式存在的,是子查询,2和3的子句出现在投影和约束条件中,是以表达式的形式存在的,是子连接。”小明不但答对了问题,而且还对问题的答案做了扩展,大明感到很一颗赛艇,于是调侃道:“腰间盘同学,你坐下,你太突出了。”
然后大明正一正颜色,继续说道“从大的方向上分类,子查询还可以分为相关子连接和非相关子连接,相关子连接是指在子查询语句中引用了外层表的列属性,这就导致外层表每获得一个元组,子查询就需要重新执行一次;而非相关子查询是指在子查询语句是独立的,和外层的表没有直接的关联,子查询可以单独执行一次,外层表可以重复利用子查询的执行结果。”
“那么一定是相关子连接才会提升了,因为我记得你在说逻辑优化原理的时候说过,相关子连接会产生‘嵌套循环’,这种情况的复杂度是O(N^2),提升上来的复杂度肯定不会比O(N^2)差,所以提升是有价值的。”小明问到。
听到这些,大明顿时碉堡了,道理虽然是这个道理,但是PostgreSQL偏偏不走寻常路,和自己之前说过的有些许差异,大明羞涩的说:“虽然话是这样说,但PostgreSQL有点不同,PostgreSQL提升了两种类型的子连接,一种是ANY类型的子连接,一种是EXISTS类型的子连接,对于ANY类型的子连接,只提升非相关子连接,而对于EXISTS类型的子连接,则只提升相关子连接。”
小明顿时想起了自己曾和同学说过相关子连接理论,当时把宿舍同学忽悠的五迷三道的,今天大明又说这可能是错的,心里不太爽利,于是怒道:“你之前教我的都有错误,那我和同学吹过的牛岂不是要成为笑柄?我感觉我受到了一万点上海。”
“小明同学不要急”,大明安抚道:“虽然PostgreSQL对于ANY类型只提升非相关的子连接,但它仍然是只提升产生嵌套循环的那种子连接,你看看这个例子。”说着大明在电脑上又敲了一个SQL语句:
1 | SELECT * FROM STUDENT WHERE sno > ANY (SELECT sno from STUDENT); |
“这是一个ANY类型的非相关子连接,但是请注意,在>前面的sno实际上产生了一个天然的相关性,这个天然的相关性就会产生嵌套循环,因此是需要提升的。我们再来看另一个语句。”大明把语句中>前面的sno换成了一个常量:
1 | SELECT * FROM STUDENT WHERE 10 > ANY (SELECT sno from STUDENT); |
“这个SQL语句中的子连接就不会提升了,因为我们把sno换成了常量,父子之间的相关性被打破了,明白了吗?”
小明点点头,心里想:子连接是否提升取决于相关性,而这个相关性不只是体现在子句里,也体现在表达式里,也就是说只要能产生嵌套循环,那就有提升的必要啊,但是。。。小明灵机一动,问道:“那ANY类型的相关子连接也会产生嵌套循环啊,为什么不提升呢?”
大明说:“这可能有点历史原因了,PostgreSQL提升ANY类型的子连接的方式和EXISTS类型的子连接的方式不同,他提升EXISTS类型的子连接的时候,是直接把子句中的表提上来做形成一个SemiJoin,可是提升ANY类型的子连接的时候,是把整个子句提上来,和父语句中的表做SemiJoin,这时候这个子句就变成了一个子查询,你看这个例子。”说着大明啪啪啪的在电脑上敲了3个语句:
1 | 1. SELECT * FROM TEST_A WHERE a > ANY (SELECT a FROM TEST_B WHERE TEST_A.b = TEST_B.b); |
“如果按照目前ANY类型子连接先提升成子查询的方式,第1个语句提升之后会变成等价于第2个语句,而第2个语句本身是无法执行的,在比较新的版本的PostgreSQL上支持了LATERAL之后,只要在第2个语句上加上LATERAL,也就是变成第3个语句就能执行了。”大明在屏幕上比划着说。
小明问道:“那岂不是说,有了LATERAL之后,ANY类型的相关子连接也能提升了?”
大明说:“只能说有一部分ANY类型的相关子连接能够提升了,比如我们上面的例1本质上就是能提升的,而且一些商业数据库确实也对这种语句做了提升,但是PostgreSQL数据库目前还没有处理这种情况。”
小明打了个哈欠说:“实在是太累了,让我们休息一下吧,查询优化器太复杂了。”
大明笑着说:“坚持不懈就能成功,万一梦想实现了呢?子连接提升之后,还有子查询提升、表达式预处理、外连接消除,不过,在这之前还是让我们先吃个鸡再说吧。”