【SQL优化案例】关于内联视图合并

浏览: 3771

感谢网友[幸福Děs'堺]提供的案例

大家好,我是来自天善BI社区的老头子,专注于BI方向,是个Oracle爱好者,同时也是ACOUG成员。今天想给大家分享一个SQL性能方面的话题 — 不合理视图合并引发的性能问题。

在开始分享具体案例之前,首先我们需要了解两个概念:视图合并和笛卡尔积。下面我来分别解释一下:

视图合并

视图合并是SQL算法生成时所发生的一种查询转换,这表示CBO在确保查询结果正确的前提下,为了产生更好的执行计划而隐式的等价改写了SQL。而视图合并的改写方式则是就是CBO把不同查询块中的对象拆分重新组合后,再把本来没有直接关联关系的表进行重写为CBO认为最优的表关联顺序。如果在一个查询块中使用了如聚集合运算、rownum等这种需要整体计算的函数的时候,CBO就无法进行视图合并,因为如果进行合并改写则可能会引起结果不正确。

如:当存在以下情况时,CBO则无法进行视图合并操作:

内联视图中含有集合运算:UNION, UNION ALL, INTERSECT, MINUS等

聚集函数:AVG, COUNT, MAX, MIN, SUM等

rownum、connect by、rollup、cube等

在CBO允许的情况下,我们可以使用/*+ MERGE(V)*/的hint来强制视图合并,但如果Oracle发现改写后SQL的结果和原始的不一致,那么即便加了hint也无法改变执行计划。

同上,在结果集不变的前提下,强制禁止视图合并的Hint:/*+ NO_MERGE */

如果Oracle没有做视图合并,那么我们可以在执行计划中看到view关键字(特殊情况除外,如临时视图等)

视图合并大概介绍这么多,大家应该有个大概的了解,那么我们为什么还要讲笛卡儿积呢?因为笛卡儿积是在SQL性能问题中一个典型的性能故障点,而在我后面分享的案例中,就遭遇了由于不当的视图合并导致错误的笛卡儿积算法。

笛卡尔积

又称直积,两个集合X和Y的笛卡尔积表示为X * Y,是所有X和Y可能组合的集合。在数据库中表现为表A和表B没有任何关联条件而产生的结果集。

例如:

X集合有3条数据{1,2,3}

Y集合有3条数据{3,4,5}

那么X和Y的笛卡尔积为:{(1,3), (1,4), (1,5), (2,3), (2,4), (2,5),(3,3), (3,4), (3,5)} 共9组数据。

但并非所有笛卡尔积都是有问题的,如:关联的其中一个集合只有一条数据时,此时笛卡尔积就不会引发性能问题,因为当X = 1时,X * Y = Y。

我之前的博客曾经写过对典型笛卡尔积引发性能问题的优化案例:

【优化案例】一次笛卡尔乘积的优化 http://www.flybi.net/blog/azzo/2012

这里再简单介绍下,例如:A、B、C三表做连接查询:

SELECT * FROM A AA, B BB, C CC
WHERE AA.ID = BB.ID
  AND BB.ID = CC.ID

那么根据SQL来看正确的关联顺序应该是A先和B表关联,再和C表关联。

然而由于统计信息不准确,或CBO bug引起的A和C两表走了关联,由于A 和 C 之间没有关联条件,从而导致低效的笛卡尔积。

基于上面的介绍,我分享一个简单的小案例由于视图合并而引起的低效笛卡尔积从而导致SQL性能低下。

案例背景:

表数据量:

ZQGS88

H1H2H3518459

HDB81

CCOD_PROV_INFO32

CC_CITY_INFO335

 Clipboard Image.png

SQL如下,要跑20s

SELECT COUNT(Z.REMOTE_URL)
  FROM ZQGS Z,
       (SELECT H.H1H2H3
          FROM H1H2H3 H, HDB D, CCOD_PROV_INFOP, CC_CITY_INFO C
         WHERE H.AREA_CODE = C.CITY_ID
           AND C.PROV_ID = P.PROV_ID
           AND P.PROV_ID = 1
           AND H.H1H2H3 LIKE D.HD || '%'
           AND D.ID = '1') M
 WHERE (Z.END_TYPE = 255 OR Z.END_TYPE = 254)
   AND Z.REMOTE_URL LIKE 'TEL:' || M.H1H2H3 || '%';
 
COUNT(Z.REMOTE_URL)
-------------------
        21
Elapsed: 00:00:20.21

预估执行计划及统计信息如下:

Clipboard Image.png

从执行计划中可以看出,SQL走了2个笛卡尔积(MERGE JOIN CARTESIAN)

第一个笛卡尔积 ID = 6:

我们暂不谈CBO评估是否有误,且认为CBO评估的rows是正确的,所以这里是因为CBO对CCOD_PROV_INFO评估只有1条数据(ID为7的行,Rows列的值评估为1),从而CBO评估笛卡尔积的连接方式代价比其他连接更低。所以这一个笛卡尔积,属于上文所提到的其中一个集合只有1条数据时,笛卡尔积是更高效的匹配方式。

第二个笛卡尔积 ID = 5:

这里就是由于HDB表和其他表并没有任何关联,而由于视图合并引发两个没有关联的表做了关联查询,从而引发的笛卡尔积。

由于开发人员说SQL中的主表是测试使用的表,即便优化后也无法模拟真实情况,于是在优化前换了个主表TBPT,重新造了数据(表结构一样,接近真实数据量),数据量为18682454(其他表不变)

TBPT18682454

H1H2H3518459

HDB81

CCOD_PROV_INFO32

CC_CITY_INFO335

SQL如下:

SELECT /*+GATHER_PLAN_STATISTICS */
 COUNT(Z.REMOTE_URL)
  FROM TBPT Z,
       (SELECT H.H1H2H3
          FROM H1H2H3 H, HDB D, CCOD_PROV_INFOP, CC_CITY_INFO C
         WHERE H.AREA_CODE = C.CITY_ID
           AND C.PROV_ID = P.PROV_ID
           AND P.PROV_ID = 1
           AND H.H1H2H3 LIKE D.HD || '%'
           AND D.ID = '1') M
 WHERE (Z.END_TYPE = 255 OR Z.END_TYPE = 254)
   AND Z.REMOTE_URL LIKE 'TEL:' || M.H1H2H3 || '%';

 

主表从ZQGS 的40条 , 增加到了TBPT表数据量的1800万条,基本跑不出数据来,所以超过5分钟我就把SQL中断掉了。

新的评估执行计划如下:

Clipboard Image.png

通过上面的执行计划看出,换了表的SQL依旧存在笛卡尔积,但由于数据量的改变,导致两个表的关联顺序发生了改变,所以性能故障点并没有改变,

1.     ID = 6 和ID = 7的两个笛卡尔积

2.     ID=12的TBPT的全表扫描

根据ID= 8的rows = 1得出ID = 7 的笛卡尔积是高效关联。所以,此SQL最大的开销在ID=6的笛卡尔积,和ID=12的TBPT的全表扫描。所以要减少这两步的cost首先要让SQL走正确的关联路径,其次在TBPT表增加索引,减少全表扫描所带来的开销。

优化点:

1. 在内联视图中增加提示: /*+ NO_MERGE */ 禁止视图合并,目的是防止无关联的表进行关联,而引起低效的笛卡尔积。

关于NO_MERGE的案例,大家也可以参考杨老师很早以前写过的一个博文:

 利用NO_MERGE解决数据字典视图访问低效http://blog.itpub.net/4227/viewspace-68569/

2. 主表TBPT的remote_url字段增加索引,目的是增加嵌套循环被驱动表的扫描速度(也可以建立remote_url + entd_type的组合索引,这样在CBO模式下,很可能选择快速索引扫描替代全表扫描,执行代价会更低)。

CREATE INDEXINDEX_TBPT_URL ON TBPT(REMOTE_URL);
 
SELECT /*+GATHER_PLAN_STATISTICS */
 COUNT(Z.REMOTE_URL)
  FROM TBPT Z,
       (SELECT /*+ NO_MERGE*/ H.H1H2H3
          FROM H1H2H3 H,HDB D, CCOD_PROV_INFO P, CC_CITY_INFO C
         WHERE H.AREA_CODE= C.CITY_ID
           AND C.PROV_ID =P.PROV_ID
           AND P.PROV_ID =1
           AND H.H1H2H3 LIKE D.HD || '%'
           AND D.ID = '1') M
 WHERE (Z.END_TYPE= 255 OR Z.END_TYPE = 254)
   ANDZ.REMOTE_URL LIKE 'TEL:' || M.H1H2H3 || '%';

优化后执行计划:

Clipboard Image.png

在这里可以看到ID=4的View关键字,说明我们已成功禁止视图的合并,让内联视图作为一个单独的整体查询后再进行和其他表的关联查询。

ID=13的索引扫描也极大减少了NestLoop的成本

测试时间:1.45s

禁用视图合并 + 索引的优化效果:

40条数据20s   -->   1800万条数据1.45s

优化点结论:

1.     由于CBO的算法缺陷或统计信息的不完整,并非所有的查询改写都是优化,有可能有些不必要的视图合并,引起了更严重的性能问题。

2.     嵌套循环的被驱动表中如果有索引将会大大提高扫描、关联的效率。

推荐 8
本文由 老头子 创作,采用 知识共享署名-相同方式共享 3.0 中国大陆许可协议 进行许可。
转载、引用前需联系作者,并署名作者且注明文章出处。
本站文章版权归原作者及原出处所有 。内容为作者个人观点, 并不代表本站赞同其观点和对其真实性负责。本站是一个个人学习交流的平台,并不用于任何商业目的,如果有任何问题,请及时联系我们,我们将根据著作权人的要求,立即更正或者删除有关内容。本站拥有对此声明的最终解释权。

11 个评论

感谢老头子的帮助和分享!
DBA就是厉害
感谢大牛的分享
我不是DBA呃。。
共同学习
谢咯
看着特别舒坦,总算搞明白你们这简单粗暴的强制CBO走高效的rule了 哈哈,给力!
期待老头子越来越多的类似案例精讲
老头子,把脚本帖个文档(方便的话),也方便大家自己测试,谢谢~
脚本没有,是我远程帮被人处理的
大佬,你的实验电脑配置和软件版本分别是多少呢?

要回复文章请先登录注册