去重改写(改写去过岳阳楼记)


【点击查看】低成本上班族靠谱副业好项目 | 拼多多无货源创业7天起店爆单玩法

【点击查看】逆林创业记 | 拼多多电商店铺虚拟类项目新玩法(附完整词表&检测工具)

【点击查看】逆林创业记 | 小白ai写作一键生成爆文速成课

领300个信息差项目,见公众号【逆林创业记】(添加请备注:网站)

先把结论写前面:

半连接会对被驱动表的连接列去重

今天优化客户的sql,遇到了这样一条sql:

select count(1)

from kb12k3 a

where a.ykb053 = '77Y334787'

and a.yka108 = '60'

and a.yka107 > 0

and exists (select 1

from kc32k1 b, ad04a1 c, kb03k4 d

where b.aae076 = c.aae076

and b.aae075 = c.aae075

and a.ykb010 = d.ykb010

and b.aae075 = d.aae075

and c.yaa012 != '1');

这一句在系统中跑5秒,其实这3个表都比较大,该sql在系统中平均跑2秒,其实还可以,但是跑的次数很多,所以我拿出来看看能不能优。

看一下这个的执行计划:

我们现在解读一下这个执行计划:首先对A表进行了过滤,得到一个结果集,然后作为驱动表去跟d表连接。

这里我们停一下去重改写,初一看,感觉没毛病。但是,我想问一下,为啥不是d b c先相互连接,然后a再跟这个结果集做连接,而是直接a就与d进行了关联呢?!

很简单,这里发生了 子查询展开,我们其实从outline部分,也能看出来这一点。所以别迷糊,这里并不是简单的半连接。

接着往下走,a与d关联得一个4047行得结果集,然后用这个结果集去和b进行嵌套循环,然后得到一个5528k的结果集,这里注意了,用这个结果集与c做嵌套循环的时候发现,id=14的starts只有1。

这里要问个为什么?

因为这里有个nest loop semi,使半连接,也就是说找到就返回结果集,那就很奇怪,我用5528k行,去另一个较大的表找,然后拿这个结果集的第一行到c表中一找就找到了,而且后面的都不找,直接把这个结果集全部返回,

这种情况,第一:太巧了;第二:c表通过条件关联出来的结果集只有1条,或者就是去重后只有一条;第三,这5528k行全是一个值。

首先,即使就那么巧,一上来就匹配上,返回结果集也不可能5528k,然后我们做一个查询,看看是不是真的c表关联出来结果集就一条?

跑很久,我直接取消了,说明肯定不止一条,然后听说你不信,我顺带把它执行计划贴出来,正好我们看看semi和单纯nl有啥区别

看到木有,111M的结果集。

所以说这里肯定去重了,我们看如下的几个查询:

可以看到,图二就解释了,为什么id=4返回的是结果集仍然是5582k行,其实每一步的结果集其实都只有一个值,但是却又很多行,但是这里只有在与c表关联的时候才发生了去重,而之前由于是nl,所以,驱动表是多少行就执行多少次循环。

这里,我们暂且能得到一个结论: 半连接会对被驱动表的连接列去重。

那其实这条sql被优化器改写成了如下的样子:

select count(1)

from kb12k3 a,kb03k4 d,kc32k1 b

where a.ykb053 = '77Y334787'

and a.yka108 = '60'

and a.yka107 > 0

and a.ykb010 = d.ykb010

and b.aae075 = d.aae075

exists (select 1

from ad04a1 c

where b.aae076 = c.aae076

and b.aae075 = c.aae075

and c.yaa012 != '1')

但是,这样的改写对么?!我不是说方式,我说的是最后的执行结果,显然是不对的!我们执行计划还没有读完!我们看id=4以上的步骤,id=3 hash unique,什么情况会在执行计划中出现这个,没错!就是用distinct的时候!然后把这个去重后的结果集

作为一个整体,相当于一个view(id=2),然后id=1才是真正做了count(1)这个操作。那我们看看id=3到底在对谁去重!

我之前的文章说过,column projection information并不是没有用,我们看id=3对应的 column projection information。

是不是看到其实这是对d表的ykb010做的distinct。为什么要这样做呢?

因为本来写这条sql的意图,最直白的理解就是,a表里面ykb010列去关联子查询的结果,在子查询的结果集中,找到了匹配的就不用往后看了,直接返回然后进行count运算。

这其实就是半连接,前面说过,半连接会对被驱动表的连接列去重,而ykb010就是连接列,所以要对d表的ykb010去重。

所以你真正被优化器改写后的sql是这样的:

select count(distinct d.ykb010)

from kb12k3 a,kb03k4 d,kc32k1 b

where a.ykb053 = '77Y334787'

and a.yka108 = '60'

and a.yka107 > 0

and a.ykb010 = d.ykb010

and b.aae075 = d.aae075

exists (select 1

from ad04a1 c

where b.aae076 = c.aae076

and b.aae075 = c.aae075

and c.yaa012 != '1')

那我们发现,这样做是不是等于兜了个圈子,走了弯路,所有这一切的罪魁祸首,其实就是unnest去重改写,也就是子查询展开,优化器之所以这么判断,当然是因为统计信息不准,但是我们不能一优化就说去把统计信息收集一遍再来。而且对于这种情况还要收集直方图

才行。

那我们就不让它展开,看下效果:

看到这里id=8是执行计划的入口,这里d表被传了一个值进来,然后一路下来,我们看到半连接还是发生再d与b关联后的结果集去驱动c,感觉跟上面的计划没什么改变, 所以我们不能光看字面。

我们看到每一步返回的行数都是1,这和我们每一步去重后的结果相符,也就是说,这里其实从真正的半连接是a表于dbc的结果集。

这样一来我们就达到了优化的目的。

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站不拥有所有权,不承担相关法律责任。如发现有侵权/违规的内容, 联系QQ3361245237,本站将立刻清除。