Oracle优化案例表连接优化.docx

上传人:b****3 文档编号:24692641 上传时间:2023-05-31 格式:DOCX 页数:13 大小:353.98KB
下载 相关 举报
Oracle优化案例表连接优化.docx_第1页
第1页 / 共13页
Oracle优化案例表连接优化.docx_第2页
第2页 / 共13页
Oracle优化案例表连接优化.docx_第3页
第3页 / 共13页
Oracle优化案例表连接优化.docx_第4页
第4页 / 共13页
Oracle优化案例表连接优化.docx_第5页
第5页 / 共13页
点击查看更多>>
下载资源
资源描述

Oracle优化案例表连接优化.docx

《Oracle优化案例表连接优化.docx》由会员分享,可在线阅读,更多相关《Oracle优化案例表连接优化.docx(13页珍藏版)》请在冰豆网上搜索。

Oracle优化案例表连接优化.docx

Oracle优化案例表连接优化

Oracle表连接的优化分享

这三个例子来源于近期一个月内我们部门遇到的经典SQL问题,都来自身边,也许都是大家非常熟悉的SQL,但是他们都有一个共同的特点,就是表连接的优化,现总结如下:

1.子查询与表连接优化(来自集团ITSM应用)

我发现身边的开发人员,很多人喜欢使用复杂子查询写各类SQL,其实很多时候,要是不注意默写细节,就会出现很多问题,如性能问题甚至结果值不对。

举个身边例子,具体如下:

-----略去

selectta.tch_id,ta.flow_id

fromtachepr,

(selectTCH_ID,c.flow_id

fromevent_qa,staff_eventb,tachec

where(a.event_type='2'ORa.event_type='1')

anda.event_id=b.event_id

and(a.flagisnullora.flag='1')

andb.staff_id=1

anda.content_id=c.tch_id)ta

whereta.flow_id=pr.sub_flow_id(+)

andpr.flow_idisnull

-------略去

这条SQL本身很复杂,我把其他部分略去,只显示典型的子查询写法的这一小部分,另外主要也是这一小部分慢,如果把这一小部分注释掉,执行速度在1秒内完成,否则需要30多秒才可以完成。

我们单看这一小部分的执行计划,截取如下:

我们可以很清楚的判断出来,该子查询的连接顺序是什么?

首先是外面的tache表先和ta结果集的tache表连接,然后再和STAFF_EVENT连接,最后和EVENT_Q完成连接!

由于staff_event这个表和TACHE表没有连接条件,就是没有体现staff_event的某某字段和TACHE表的某某字段关联的地方,所以这里产生了笛卡尔乘积!

因此本次查询非常的慢。

该如何处理呢?

其实很简单,我们发现,整个语句最终只返回42条

说明ta这个结果集本身返回的记录数并不多,虽然tache是一张大表,但是这段语句如果没有笛卡尔乘积,应该非常快!

简单研究发现,只要ta结果集内部先完成表连接,再和外部连接,就不至于产生笛卡尔乘积了,因为在ta结果集里,我们可以STAFF_EVENTb和EVENT_Qa先完成连接,他们是有连接条件的,条件是

a.event_id=b.event_id

接下来这两个连接再和ta结果集中的STAFFc表连接,又有连接条件,条件是:

a.content_id=c.tch_id

最好再和外面的tachepr表完成连接,连接条件是:

ta.flow_id=pr.sub_flow_id(+)

如此看来,不可能有笛卡尔!

而如何让ORACLE的子查询不要先自行逛街出去和外面表先连接,再会过来连接内部剩下的部分呢?

很简单,只要修改为如下即可,增加rownum部分

selectta.tch_id,ta.flow_id

fromtachepr,

(selectTCH_ID,c.flow_id,rownum

fromevent_qa,staff_eventb,tachec

where(a.event_type='2'ORa.event_type='1')

anda.event_id=b.event_id

and(a.flagisnullora.flag='1')

andb.staff_id=1

anda.content_id=c.tch_id)ta

whereta.flow_id=pr.sub_flow_id(+)

andpr.flow_idisnull

为了保证rownum的结果集是对的,oracle不可能先出去和结果集外的表关联一部分再回到结果集内,只可能内部先关联了,因此,我们成功了!

最终执行计划如下,笛卡尔乘积消失,总的SQL语句执行时间从30秒变为1秒

 

2.NL连接与表连接优化

在我们电信行业,80%的表连接都是NL连接,因为这是由oltp系统的特性决定的:

“虽然数据量很大,查询更新很频繁,但是最终需要查询返回及更新的数据量却很少。

但是NL连接应用不当,却是有致命的问题,因为NL最大的特点是驱动表返回多少条,被驱动表访问多少次,所以NL连接在驱动表返回极少的时候,性能不低,但是驱动表返回很多的时候,性能却很低下,万万不可使用!

下面再举一个身边的例子,来自安徽拨测应用的一条SQL,具体如下:

--略去

from(selecta.alarm_title,

--略去其他字段

fromne_alarm_lista

wherea.alarm_type=20

anda.alarm_Levelin('1','2')

anda.alarm_statein(0)

anda.last_send_time>=TO_DATE('&P_DATE_BEGIN'||'00:

00:

00','YYYY-MM-DDhh24:

mi:

ss')

anda.last_send_time<=TO_DATE('&P_DATE_END'||'23:

59:

59','YYYY-MM-DDhh24:

mi:

ss')

)a1,

manage_regionb,

net_elementd,

(select*

fromtp_domain_listvalues

wheredomain_codeLIKE'DOMAIN_ALARM_STATE%')f,

kpi_code_listg,

ne_alarm_msg_source_relah,

net_elementk,

manage_regionl

wherea1.alarm_region_origin=b.region_id(+)

anda1.ne_id=d.ne_id

--andinstr(d.path,:

P_NE_ID,1,1)>0

anda1.alarm_state=f.list_value

anda1.kpi_id=g.kpi_id

anda1.ne_alarm_list_id=h.ne_alarm_msg_id

andh.source_type='19'

andh.source_id=k.ne_id

andk.region_id=l.region_id

andl.region_idin('&PSOURCE_REGION')

orderbya1.alarm_state,a1.create_timedesc

 

截取部分执行计划分析,我们惊奇的发现,ORACLE预估NE_ALARM_MSG_SOURCE_RELA表source_type=’19’的条件是返回1条,实际返回了477K条,预估的严重不准确!

怪不得会使用NL连接。

结果NL连接导致后续的被驱动表被访问了477K次,真是惊人的数字!

那该如何优化呢?

优化招式没有定式,有的时候要改写SQL,有的时候需要增加索引,而有的时候,却只要重新收集一下表的统计信息或者直方图即可。

而此时,我们非常清楚的判断出来,ORACLE对NE_ALARM_MSG_SOURCE_RELA表的SOURCE_TYPE列的数据分别严重错误了。

实际这张表记录有快200万,而这个表的SOURCE_TYPE的列只有3个取值,都是大量的返回。

因此我们要做的事情非常简单,重新收集这个表的列的直方图即可,如下:

execdbms_stats.gather_table_stats(ownname=>'BOSSWG',tabname=>'NE_ALARM_MSG_SOURCE_RELA',estimate_percent=>10,method_opt=>'forallcolumnssize254',cascade=>TRUE);

新的执行计划如下从NL更新为部分HASH了。

执行时间从原先的20秒缩减为0.2秒

3.NOTIN与表连接优化

很多人说不要用NOTIN,要考虑用NOTEXISTS,其实很多时候NOTIN是可以很快的,而且很多场合NOTIN也和NOTEXISTS不完全等价,在列明确为非空的时候,NOTIN在可以用到ANTI算法的时候,是可以很快的,我们可以看看SQL优化

SELECTT.FLOW_ID

FROMFLOW_HIST

WHERET.FLOW_MOD=11263

ANDT.FLOW_IDNOTIN(SELECTZJ.FLOW_IDfromZJ_EVENT_HISZJ)

执行计划如下:

该SQL是NOTIN写法的SQL,理论上应该是anti算法的执行计划最高效,但是却是走filter,因此效率极低,在确定ZJ_EVENT_HIS表的FLOW_ID字段不会为空后,将该语句增加whereflow_idisnotnull,或者将该列的属性修改为NOTNULL属性。

修改为如下,效率可以提升很多,从1万多秒缩减为1秒,具体修改如下:

SELECTT.FLOW_ID

FROMFLOW_HIST

WHERET.FLOW_MOD=11263

ANDT.FLOW_IDNOTIN

(SELECTZJ.FLOW_IDFROMZJ_EVENT_HISZJwhereflow_idisnotnull)

如果这样修改,执行计划将会边为如下

/*

结论:

NL连接中,驱动表被访问0或者1次,被驱动表被访问0次或者N次,N由驱动表返回的结果集的条数来定)

*/

--环境构造

--研究NestedLoopsJoin的表访问次数前准备工作

DROPTABLEt1CASCADECONSTRAINTSPURGE;

DROPTABLEt2CASCADECONSTRAINTSPURGE;

CREATETABLEt1(

idNUMBERNOTNULL,

nNUMBER,

contentsVARCHAR2(4000)

;

CREATETABLEt2(

idNUMBERNOTNULL,

t1_idNUMBERNOTNULL,

nNUMBER,

contentsVARCHAR2(4000)

;

executedbms_random.seed(0);

INSERTINTOt1

SELECTrownum,rownum,dbms_random.string('a',50)

FROMdual

CONNECTBYlevel<=100

ORDERBYdbms_random.random;

INSERTINTOt2SELECTrownum,rownum,rownum,dbms_random.string('b',50)FROMdualCONNECTBYlevel<=100000

ORDERBYdbms_random.random;

COMMIT;

selectcount(*)fromt1;

selectcount(*)fromt2;

 

--我们用设置statistics_level=all的方式来观察如下表连接语句的执行计划:

--T2表被访问100次(驱动表访问1次,被驱动表访问100次)

--这个setlinesize1000对dbms_xplan.display_cursor还是有影响的,如果没有设置,默认情况下的输出,将会少了很多列,如BUFFERS等

Setlinesize1000

altersessionsetstatistics_level=all;

SELECT/*+leading(t1)use_nl(t2)*/*

FROMt1,t2

WHEREt1.id=t2.t1_id;

--略去记录结果

select*fromtable(dbms_xplan.display_cursor(null,null,'allstatslast'));

-------------------------------------------------------------------------------------

|Id|Operation|Name|Starts|E-Rows|A-Rows|A-Time|Buffers|

-------------------------------------------------------------------------------------

|0|SELECTSTATEMENT||1||100|00:

00:

00.94|100K|

|1|NESTEDLOOPS||1|100|100|00:

00:

00.94|100K|

|2|TABLEACCESSFULL|T1|1|100|100|00:

00:

00.01|14|

|*3|TABLEACCESSFULL|T2|100|1|100|00:

00:

00.94|100K|

-------------------------------------------------------------------------------------

3-filter("T1"."ID"="T2"."T1_ID")

---换个语句,这次T2表被访问2次(驱动表访问1次,被驱动表访问2次)

Setlinesize1000

altersessionsetstatistics_level=all;

SELECT/*+leading(t1)use_nl(t2)*/*

FROMt1,t2

WHEREt1.id=t2.t1_id

ANDt1.nin(17,19);

select*fromtable(dbms_xplan.display_cursor(null,null,'allstatslast'));

-------------------------------------------------------------------------------------

|Id|Operation|Name|Starts|E-Rows|A-Rows|A-Time|Buffers|

-------------------------------------------------------------------------------------

|0|SELECTSTATEMENT||1||2|00:

00:

00.02|2019|

|1|NESTEDLOOPS||1|2|2|00:

00:

00.02|2019|

|*2|TABLEACCESSFULL|T1|1|2|2|00:

00:

00.01|8|

|*3|TABLEACCESSFULL|T2|2|1|2|00:

00:

00.02|2011|

-------------------------------------------------------------------------------------

2-filter(("T1"."N"=17OR"T1"."N"=19))

3-filter("T1"."ID"="T2"."T1_ID")

--继续换个语句,这次T2表被访问1次(驱动表访问1次,被驱动表访问1次)

Setlinesize1000

altersessionsetstatistics_level=all;

SELECT/*+leading(t1)use_nl(t2)*/*

FROMt1,t2

WHEREt1.id=t2.t1_id

ANDt1.n=19;

select*fromtable(dbms_xplan.display_cursor(null,null,'allstatslast'));

SQL>select*fromtable(dbms_xplan.display_cursor(null,null,'allstatslast'));

-------------------------------------------------------------------------------------

|Id|Operation|Name|Starts|E-Rows|A-Rows|A-Time|Buffers|

-------------------------------------------------------------------------------------

|0|SELECTSTATEMENT||1||1|00:

00:

00.01|1014|

|1|NESTEDLOOPS||1|1|1|00:

00:

00.01|1014|

|*2|TABLEACCESSFULL|T1|1|1|1|00:

00:

00.01|8|

|*3|TABLEACCESSFULL|T2|1|1|1|00:

00:

00.01|1006|

-------------------------------------------------------------------------------------

2-filter("T1"."N"=19)

3-filter("T1"."ID"="T2"."T1_ID")

 

---接下来,T2表居然被访问0次(驱动表访问1次,被驱动表访问0次)

SELECT/*+leading(t1)use_nl(t2)*/*

FROMt1,t2

WHEREt1.id=t2.t1_id

ANDt1.n=999999999;

select*fromtable(dbms_xplan.display_cursor(null,null,'allstatslast'));

-------------------------------------------------------------------------------------

|Id|Operation|Name|Starts|E-Rows|A-Rows|A-Time|Buffers|

-------------------------------------------------------------------------------------

|0|SELECTSTATEMENT||1||0|00:

00:

00.01|7|

|1|NESTEDLOOPS||1|1|0|00:

00:

00.01|7|

|*2|TABLEACCESSFULL|T1|1|1|0|00:

00:

00.01|7|

|*3|TABLEACCESSFULL|T2|0|1|0|00:

00:

00.01|0|

-------------------------------------------------------------------------------------

2-filter("T1"."N"=999999999)

3-filter("T1"."ID"="T2"."T1_ID")

---到最后,不只是T2表被访问0次,连T1表也访问0次

SELECT/*+leading(t1)use_nl(t2)*/*

FROMt1,t2

WHEREt1.id=t2.t1_id

AND1=2;

select*fromtable(dbms_xplan.display_cursor(null,null,'allstatslast'));

----------------------------------------------------------------------------

|Id|Operation|Name|Starts|E-Rows|A-Rows|A-Time|

----------------------------------------------------------------------------

|0|SELECTSTATEMENT||1||0|00:

00:

00.01|

|*1|FILTER||1||0|00:

00:

00.01|

|2|NESTEDLOOPS||0|100|0|00:

00:

00.01|

|3|TABLEACCESSFULL|T1|0|100|0|00:

00:

00.01|

|*4|TABLEACCESSFULL|T2|0|1|0|00:

00:

00.01|

----------------------------------------------------------------------------

1-filter(NULLISNOTNULL)

4-filter("T1"."ID"="T2"."T1_ID")

 

--分析T2表被访问次数不同的原因

---解释T2表为啥被访问100次

selectcount(*)fromt1;

COUNT(*)

----------

100

---解释T2表为啥被访问2次

selectcount(*)fromt1wheret1.nin(17,19);

COUNT(*)

----------

2

---解释T2表为啥被访问1次

selectcount(*)fromt1wheret1.n=19;

COUNT(*)

----------

1

---解释T2表为啥被访问0次

selectcount(*)fromt1wheret1.n=999999999;

C

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 自然科学 > 生物学

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1