前一篇介绍了connect by的用法
https://blog.csdn.net/u011165335/article/details/82822224

这里在看看如何优化:
环境跟上一篇的一样:11g

-- 表结构
drop table   menu;
create table menu(mid varchar2(64) not null,parent_id varchar2(64) not null,mname varchar2(100) not null,mdepth number(2) not null,primary key (mid)
);-- 初始化数据
-- 顶级菜单
insert into menu values ('100000', '0', '顶级菜单1', 1);
insert into menu values ('200000', '0', '顶级菜单2', 1);
insert into menu values ('300000', '0', '顶级菜单3', 1); -- 父级菜单
-- 顶级菜单1 直接子菜单
insert into menu values ('110000', '100000', '菜单11', 2);
insert into menu values ('120000', '100000', '菜单12', 2);
insert into menu values ('130000', '100000', '菜单13', 2);
insert into menu values ('140000', '100000', '菜单14', 2);
-- 顶级菜单2 直接子菜单
insert into menu values ('210000', '200000', '菜单21', 2);
insert into menu values ('220000', '200000', '菜单22', 2);
insert into menu values ('230000', '200000', '菜单23', 2);
-- 顶级菜单3 直接子菜单
insert into menu values ('310000', '300000', '菜单31', 2); -- 菜单13 直接子菜单
insert into menu values ('131000', '130000', '菜单131', 3);
insert into menu values ('132000', '130000', '菜单132', 3);
insert into menu values ('133000', '130000', '菜单133', 3);-- 菜单132 直接子菜单
insert into menu values ('132100', '132000', '菜单1321', 4);
insert into menu values ('132200', '132000', '菜单1332', 4);

在10.2.0.2之后对于递归查询,CBO可以有2种选择:
connect_by_filtering和no_connect_by_filtering(10.2.0.2版本引入),另外还有一些隐藏参数也影响着递归查询的执行计划。
参数
KSPPINM KSPPSTVL KSPPDESC


_optimizer_connect_by_cost_based TRUE use cost-based transformation for connect by
_optimizer_connect_by_combine_sw TRUE combine no filtering connect by and start with
_optimizer_connect_by_elim_dups TRUE allow connect by to eliminate duplicates from input
_optimizer_connect_by_cb_whr_only FALSE use cost-based transformation for whr clause in connect

hint:NO_CONNECT_BY_FILTERING/CONNECT_BY_FILTERING
在原来的算法中(CONNECT BY WITH FILTERING),会在每次循环中作行过滤操作,
然后只读取需要的行.
而在新的算法中(CONNECT BY NO FILTERING WITH SW),则会把所有需要的数据读入内存,
然后统一作内存排序来过滤掉不需要的列.
猜想:(未经验证)
前一种算法适用于从数据集中取很小一部分数据的场景,
而后一种算法则适用于从大数据量中取大量数据的操作.
但由于oracle对connect by语句cardinality估算的不准确性,
关于不准确性这里可以看之前我转载的一篇文章:
https://blog.csdn.net/u011165335/article/details/82819455
导致oracle并不能准确地在两种算法间作出合理的抉择.

猜想:cbo在选择2种算法时,是基于成本考虑的;
如果不想让执行计划走CONNECT BY NO FILTERING WITH START-WITH,
可以:
1.使用hint:connect_by_filtering
2.11g中新增的隐含参数’_optimizer_connect_by_elim_dups’和’_connect_by_use_union_all’;
禁用即可:也可以在session级别设置
alter system set “_optimizer_connect_by_elim_dups” = false;
alter system set “_connect_by_use_union_all” = “old_plan_mode”;
禁用之后,只会走connect by filtering

分2个部分:
第一个部分:看看connect by 的执行过程
第二个部分:看看start with涉及子查询以及视图合并的场景

下面开始第一个部分:

drop table  menu_temp;
create table menu_temp
as
select * from menu;--1.不加任何索引
select /*+ connect_by_filtering*/ aa.*,levelfrom menu_temp aastart with aa.mid = '130000'
connect by  prior aa.mid = aa.parent_id;
查看对应的预估执行计划和高级执行计划



此时走的时候connect by with filtering
执行过程:
id=2,把开始要循环的数据取出来;猜想每次是走的filter;
select * from menu_temp aa where aa.mid=‘130000’;

这里得到的mid为10000;
然后CONNECT BY PUMP接收第一步扫描出的结果parent_id=100000的数据,用临时表 0 02 跟 m e n u t e m p 做 h a s h 连 接 ( 连 接 列 p a r e n t i d ) , 把 得 到 的 结 果 m i d , 放 入 _002跟menu_temp做hash连接(连接列parent_id),把得到的结果mid,放入 0​02跟menut​emp做hash连接(连接列parenti​d),把得到的结果mid,放入_002,再跟menu_temp做hash连接,直到最后的parent_id没有对应的mid时,停止循环,直到达到最后一个层级。
menu_temp的访问次数为3+1=4次;
可以发现,此时的表访问次数为:max(level)+1;
这里走的全表,如果表很大,层级很多,那么这种方式的全表范问,必然造成效率低下;
而且此时的预估数是16(全表数),实际上是6;
这里cbo无法估算准确的结果数,也是一个问题;

再看走no filtering的
select *from menu_temp aastart with aa.mid = '130000'
connect by  prior aa.mid = aa.parent_id;


默认此时CBO走的是connect by no filtering with start-with算法;
逻辑读为15;

再看看真实的执行计划:
alter session set statistics_level=all;
select * from table(dbms_xplan.display_cursor(null,null,‘advanced allstats last -projection’));

这种算法下:表的访问次数为1;具体如何实现,目前不太清楚…
但是从outline来看,内部处理似乎还是用到了临时表,只不过在普通的执行计划里面看不出来;
对比发现,此时走connect by no filtering with start-with效率更高;
逻辑读以及代价都比第一种少;

上面的都是没建索引的
--2建立索引看看:
create index   menu_mid_ind on menu_temp(mid);
create index   min_parent_id_ind on menu_temp(parent_id);select aa.*,levelfrom menu_temp aastart with aa.mid = '130000'
connect by  prior aa.mid = aa.parent_id;



放大部分真实的执行计划:可以发现有索引的情况,估算的返回值还相对准确的;
为总数/列基数

此时:NL走了3次;menu_temp的索引一共访问了6+1=7次;
而如果全表的是4次;这就需要权衡了,走索引不一定比全表快;(这里先不讨论这个…)

第一次访问索引: select * from menu_temp aa where aa.mid=‘130000’;
然后再看NL的3次:
首先第一次NL:pump的临时表把parent_id=100000的结果集:1行;
传递给ment_temp的索引,走了1次;
第二次NL:pump的临时表的parent_id=130000的结果集:3行;
传递给ment_temp的索引:重复走3次;
第三次NL:
pump的临时表的
parent_id=133000的结果集为0.在ment_temp索引没有记录,次数为0;
parent_id=132000的结果集为2,在ment_temp索引被驱动2次;
parent_id=133000的结果集为0.在ment_temp索引没有记录,次数为0;

然后是parent_id=132200,132100,均没有记录,在ment_temp索引范问次数为0;
于是这里ment_temp的索引访问次数为:1+3+2=6;

通过执行计划可以看到这里虽然在子节点和父节点都建立了索引,但是如果我只查mid
如:
select aa.mid
from menu_temp aa
start with aa.mid = ‘130000’
connect by prior aa.mid = aa.parent_id;

可以发现依然有回表现象,那么如何解决回表呢?
继续看走no filtering的执行计划

select /*+ NO_CONNECT_BY_FILTERING */*from menu_temp aastart with aa.mid = '130000'
connect by  prior aa.mid = aa.parent_id;
第二次跑,有缓存,所以此时的逻辑为3;

从代价和逻辑读来看,效率比上面的走索引好;
此时走的还是全表扫,为啥不走索引呢?
是不是因为预估的返回记录数为全表的16,所以cbo认为时走全表呢?
select /*+ NO_CONNECT_BY_FILTERING  cardinality(aa 1) */*from menu_temp aastart with aa.mid = '130000'
connect by  prior aa.mid = aa.parent_id;
等价:
select /*+ NO_CONNECT_BY_FILTERING  cardinality(AA@SEL$2 1) */*from menu_temp aastart with aa.mid = '130000'
connect by  prior aa.mid = aa.parent_id;


可以发现,即使为1,cbo选择的还是全表扫;
为啥此时还是走的全表扫呢? 稍等…
看下面的强制走索引
可不可以强制走索引?
select /*+ NO_CONNECT_BY_FILTERING index(aa) */aa.mid
from menu_temp aa
start with aa.mid = ‘130000’
connect by prior aa.mid = aa.parent_id;

可以发现,虽然此时强制走的是索引,但是走的是索引全扫描,单块读,且回表,在返回数据量多的情况下,效率并不高;而且我的返回值只有aa.mid,按道理此时不应该会有回表啊,于是猜想在内部的表连接时产生了回表:
内部应该存在 temp1.mid=某个值 and temp1.parent_id=某个值
这种关联关系;
于是再建立一个组合索引就可以去掉内部回表,此时查询CBO就可以自动走索引了:
drop index MIN_PARENT_ID_IND ;
drop index MENU_MID_IND;
删除这2个之前建立的索引,防止干扰;

drop index mid_parent_ind;
create index mid_parent_ind on menu_temp(mid,parent_id);
select /+ NO_CONNECT_BY_FILTERING index_ffs(aa)/aa.mid,aa.parent_id
from menu_temp aa
start with aa.mid = ‘130000’
connect by prior aa.mid = aa.parent_id;



可以发现,建立了组合索引后,可以成功的走ffs索引;说明回表已经被消除了!
逻辑读为4,代价为5,比全表扫的7小;
好,不加hint呢?
select aa.mid,aa.parent_id
from menu_temp aa
start with aa.mid = ‘130000’
connect by prior aa.mid = aa.parent_id;

可以发现,此时cbo走的是索引全扫描,且逻辑读极小为1,代价也最小为3;
比指定的ffs效率还好!
如果查询的是所有列呢?
select /+ cardinality(aa 2)/ aa.*
from menu_temp aa
start with aa.mid = ‘130000’
connect by prior aa.mid = aa.parent_id;

此时因为需要回表,于是cbo选择走了全表扫;
而且这里我故意指定了menu_temp的返回结果数为2,但是cbo依然选择了走全表扫;
在NO_CONNECT_BY_FILTERING这种执行计划下,cbo似乎很害怕回表,不管返回的记录数是多少,它见到回表操作,就决然的选择了走全表扫;难道cbo认为 只要有回表,代价就比全表低?这点不是很理解;

那么在有回表的情况走索引,真的效率就低吗?
select /+ index(aa)/ aa.*
from menu_temp aa
start with aa.mid = ‘130000’
connect by prior aa.mid = aa.parent_id;

在强制走索引下,可以看到,这里的逻辑读和代价都比上面的全表的效率要好;
cbo为啥就不考虑一下呢?
说明了cbo在这种场景下是需要改进的;

关于第一个部分做个小结:
在10.2.0.2版本会,connect by查询执行计划会有2种算法可以选择:
1.connect_by_filtering
这种类似filter操作,只不过filter的第二个部分为pump和menu_temp的递归连接查询,可以有嵌套或者hash join;
1.1在返回数据量比较小的时候,适合走NL,此时可以在要递归的子节点和父节点列建立组合索引(不建议分别建索引);
这样在nl的时候,都可以走同一个索引;
但是要注意回表消耗和索引的查询次数;
索引的查询次数应该近似为表的查询结果数;
所以在返回量大的情况下,如果不能避免回表,不如走全表;
1.2在返回的数据量大的时候,递归部分可以走hash,此时menu_temp为全表扫,
表的访问次数为max(level)+1;
所以,如果一定要走connect_by_filtering的话,此时只能尽量减少表menu_temp的访问体积,即在访问的时候,用过滤后的with临时表替代;

2.no_connect_by_filtering
从刚才上面的测试来看,效率还不错,但是场景有限,
在实际的问题分析中:
还是要比较2种算法谁的效率更高;
如何简单的比较呢?先建立组合索引,再分别比较这2种算法啦;

在no_connect_by_filtering场景下,表只会被访问次一次,似乎告诉我们,这个算法适合返回大量数据的场景;具体看以后的实际问题测试吧…
为了避免内部回表,此时需要建立一个组合索引(子节点列,父节点列);
1.1 当查询的结果列为 子节点或者父节点列时,
此时可以走fs或者ffs;效率最佳;因为不会回表;
fs内部走的NL,ffs内部走的hash
1.2 当查询的结果列不仅仅是子节点或者父节点列时,
此时CBO默认是会走全表扫的;但是可以强制走索引,具体走不走索引;以实际场景分析为准;

好,再看第二部分:关于start with的子查询以及视图
1.子查询

menu_temp还是上面的表;
有一个组合索引
drop index  mid_parent_ind;
create index mid_parent_ind on menu_temp(mid,parent_id);--先模拟一个最简单的子查询
select aa.*from menu_temp aastart with aa.mid in(select  '130000' from dual)
connect by  prior aa.mid = aa.parent_id;

这里CBO主动选择了no filtering with SW;
此时的代价为9;逻辑读为3;
并且是在最后一步来过滤开始的条件;

–不回表呢
select aa.mid
from menu_temp aa
start with aa.mid in(select ‘130000’ from dual)
connect by prior aa.mid = aa.parent_id;

此时走的FS,代价为5;逻辑读为1,近乎减少了1倍;
也可以知道主要的消耗还是在于递归查询;

--再看看走filtering的
select /*+ connect_by_filtering*/ aa.*from menu_temp aastart with aa.mid in(select '130000' from dual)
connect by  prior aa.mid = aa.parent_id;


可以发现,效率显然没有no_filtering的好;
这里的子查询展开了(二级形态),跟menu_temp走nl内连接;没问题;
此时主要的消耗在于递归查询;
另外,这里的start with部分做的这个查询转换,是内部的;
所以无法显式的等价改写…
只能控制展开与不展开;
小结一下:
这里如果存在性能问题:
1.子查询跟主表的连接方式是否存在问题,有问题就先优化子查询问题;
2如果没问题那么就看看递归查询的效率;

再看看视图合并的

select * from (select e.empno,e.mgr from emp e,dept dwhere e.deptno=d.deptnoand   d.dname like '%A%'
) tt
start with tt.empno=7369
connect by prior tt.empno=tt.mgr;


这个明显是进行了视图合并;

等价下面的sql:
select e.empno, e.mgr
from emp e, (select * from dept d where d.dname like ‘%A%’) d
where e.deptno = d.deptno
start with e.empno in (7369)
connect by prior e.empno = e.mgr;

能等价改写,再优化相关的问题就方便了;
(比如刚在网上看一个一个connect by查询问题:http://www.itpub.net/thread-1903074-1-1.html
第三页有我的回复;)
这里改写的关键在于不能出现and=‘常量’;

再比如:
select *from (select e.empno, e.mgrfrom emp e, dept d, salgrade sswhere e.deptno = d.deptnoand e.ename = d.dnameand ss.losal = e.saland d.dname like '%A%') ttstart with tt.empno = 7369
connect by prior tt.empno = tt.mgr;
等价:
select e.empno, e.mgrfrom emp e,(select * from dept d where d.dname like '%A%') d,salgrade sswhere e.deptno = d.deptnoand e.ename = d.dnameand ss.losal = e.salstart with e.empno = 7369
connect by prior e.empno = e.mgr;

小结一下:
关于这种视图合并的,先要看懂执行计划,是哪一块出了问题,如果cbo进行了视图合并,那么你就要知道如何合并的,并能自已改写出来,这样你就能针对性的控制你想控制的部分;
好了,就先写到这了,有不对的地方还请指正,谢谢(∩_∩)

2018年9月23日21:36:32 by ysy

oracle connect by优化小探相关推荐

  1. ORACLE+SQL性能优化

      1. 访问Table的方式 ORACLE 采用两种访问表中记录的方式: a. 全表扫描 全表扫描就是顺序地访问表中每条记录. ORACLE采用一次读入多个数据块(database block)的方 ...

  2. Oracle 12c数据库优化器统计信息收集的最佳实践

    Oracle 12c数据库优化器统计信息收集的最佳实践 转载自     沃趣科技(ID:woqutech) 作者         刘金龙(译) 原文链接   http://www.oracle.com ...

  3. Oracle服务器性能优化

    几个简单的步骤大幅提高Oracle性能--我优化数据库的三板斧 数据库优化的讨论可以说是一个永恒的主题.资深的Oracle优化人员通常会要求提出性能问题的人对数据库做一个statspack,贴出数据库 ...

  4. 基于oracle的sql优化

    [基于oracle的sql优化] 基于oracle的sql优化 [博主]高瑞林 [博客地址]http://www.cnblogs.com/grl214 一.编写初衷描述 在应有系统开发初期,由于数据库 ...

  5. 数据分片排序oracle,Oracle数据库的优化

    摘要 本文提出了一种优化Oracle数据库的方法.Oracle中SQL语句的执行过程可分为解析(Parse).执行(Execute)和提取结果(Fetch)三步,此方法就是通过对SQL语句在Oracl ...

  6. oracle使用 union all 用自增序列_值得收藏的Oracle数据库性能优化

    值得收藏的Oracle数据库性能优化 年尾了,新的一波面试军又要开始了,被问到最多的可能就是性能优化,尤其是数据库性能优化,这个面试题不管是初中高级工程师都会被问到.因此我觉得下面31点ORACLE优 ...

  7. ORACLE语句基本优化

    一.操作符优化 1.IN 操作符 用IN写出来的SQL的优点是比较容易写及清晰易懂,这比较适合现代软件开发的风格.但是用IN的SQL性能总是比较低的,从Oracle执行的步骤来分析用IN的SQL与不用 ...

  8. Oracle数据性能优化

    Oracle 应用系统的优化四个方面 1. 应用程序SQL语句优化; 2. ORACLE数据库参数调整; 3. 操作系统参数调整; 4. 网络性能调整. oracle应用系统的性能指标 1. 数据库吞 ...

  9. 一个修改Oracle用户密码的小诀窍

    一个修改Oracle用户密码的小诀窍 数据库版本: 9.2.0 .5 有时候我们可能不知道一个用户的密码,但是又需要以这个用户做一些操作,又不能去修改掉这个用户的密码,这个时候,就可以利用一些小窍门, ...

最新文章

  1. 第四章:Spring项目文件上传两种方式(全解析)
  2. java concurrenthashmap与阻塞队列
  3. HttpRequest 与HttpWebRequest 有什么区别
  4. Jenkins持续集成环境之Maven的安装和配置
  5. lisp精要(2)-基础(1)
  6. DataTable添加行的方法
  7. cogs 76. [NOIP2007] 统计数字
  8. ios web页面测试方法
  9. openssh升级_Redhat 6.5源码编译升级openssh到7.8版本
  10. 获取props里面的数据_Kafka 使用Java实现数据的生产和消费demo
  11. SVN创建,切换,合并,删除分支
  12. oracle analyze原理,Oracle analyze 介绍
  13. 网络故障排查简单思路(思路见目录)
  14. 圣诞节,教你用Python给微信头像添加一个圣诞帽
  15. php 知乎源代码,PHP最新仿知乎问答社区源码下载带行业打赏问答支持文章、话题、第三方登录、文章和问题打赏...
  16. ​草莓熊python turtle绘图代码(玫瑰花版)附源代码
  17. 利用JNative调用MediaInfo.dll
  18. 2022-2027年中国棉纺织机械行业发展监测及投资战略研究报告
  19. 一个小巧,也很nice的“小日历”--一个Android App
  20. Linux下Patch补丁的生成与打补丁命令

热门文章

  1. 【FPGA教程案例42】图像案例2——通过verilog实现图像二值化处理,通过MATLAB进行辅助验证
  2. 树莓派上Python实现TSL2561采样光照强度
  3. 打印机共享疑难杂症(不断更新...)
  4. amr文件怎么转换成mp3,实用教程
  5. Unity 编辑器扩展:小说阅读器
  6. 网页中播放FLV视频文件的代码
  7. Js逆向教程20-Hook基础
  8. dw 复选框 php输出,Dreamweaver CS3的复选框使用方法
  9. [附源码]Python计算机毕业设计办公用品管理系统
  10. 从0开始 独立完成企业级Java电商网站开发(服务端)