ITPub博客

首页 > 数据库 > Oracle > 半连接会对被驱动表的驱动列去重

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

原创 Oracle 作者:燕鑫 时间:2018-05-17 15:32:39 1 删除 编辑

    先把结论写前面:
    半连接会对被驱动表的连接列去重

    今天优化客户的sql,遇到了这样一条sql:
select count(1)
  from kb12k3 a
 where a.ykb053 = '77Y09100117070334787'
   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 = '77Y09100117070334787'
   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 = '77Y09100117070334787'
   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的结果集。
     这样一来我们就达到了优化的目的。


来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/31532509/viewspace-2154615/ ,如需转载,请注明出处,否则将追究法律责任。

上一篇: 打补丁PSU
下一篇: 没有了~
请登录后发表评论 登录
全部评论

注册时间:2018-04-17

  • 博文量
    6
  • 访问量
    3770