项目中的表连接优化分享

项目中的表连接优化分享
项目中的表连接优化分享

表连接的优化分享

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

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

我发现身边的开发人员,很多人喜欢使用复杂子查询写各类SQL,其实很多时候,要是不注意默写细节,就会出现很多问题,如性能问题甚至结果值不对。举个身边例子(来自ITSM集团项目的一条SQL)具体如下:

-----略去

select ta.tch_id, ta.flow_id

from tache pr,

(select TCH_ID, c.flow_id

from event_q a, staff_event b, tache c

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

and a.event_id = b.event_id

and (a.flag is null or a.flag = '1')

and b.staff_id = 1

and a.content_id = c.tch_id) ta

where ta.flow_id = pr.sub_flow_id(+)

and pr.flow_id is null

-------略去

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

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

我们可以很清楚的判断出来,该子查询的连接顺序是什么?首先是外面的tache 表先和ta结果集的tache 表连接,然后再和STAFF_EVENT连接,最后和EVENT_Q完成连接!

由于staff_event 这个表和TACHE表没有连接条件,就是没有体现staff_event的某某字段和TACHE表的某某字段关联的地方,所以这里产生了笛卡尔乘积!因此本次查询非常的慢。

该如何处理呢?其实很简单,我们发现,整个语句最终只返回42条

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

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

a.event_id =

b.event_id

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

a.content_id = c.tch_id

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

ta.flow_id = pr.sub_flow_id(+)

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

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

select ta.tch_id, ta.flow_id

from tache pr,

(select TCH_ID, c.flow_id,rownum

from event_q a, staff_event b, tache c

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

and a.event_id = b.event_id

and (a.flag is null or a.flag = '1')

and b.staff_id = 1

and a.content_id = c.tch_id) ta

where ta.flow_id = pr.sub_flow_id(+)

and pr.flow_id is null

为了保证rownum的结果集是对的,oracle不可能先出去和结果集外的表关联一部分再回到结果集内,只可能内部先关联了,因此,我们成功了!最终执行计划如下,笛卡尔乘积消失,总的SQL语句执行时间从30秒变为1秒

2.NL连接与表连接优化(来自安徽拨侧应用)

在我们电信行业,80%的表连接都是NL连接,因为这是由oltp 系统的特性决定的:“虽然数据量很大,查询更新很频繁,但是最终需要查询返回及更新的数据量却很少。”

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

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

--略去

from (select a.alarm_title,

--略去其他字段

from ne_alarm_list a

where a.alarm_type = 20

and a.alarm_Level in ('1', '2')

and a.alarm_state in (0)

and https://www.360docs.net/doc/d412110580.html,st_send_time >= TO_DATE('&P_DATE_BEGIN' || ' 00:00:00', 'YYYY-MM-DD hh24:mi:ss')

and https://www.360docs.net/doc/d412110580.html,st_send_time <= TO_DATE('&P_DATE_END' || ' 23:59:59', 'YYYY-MM-DD hh24:mi:ss')

) a1,

manage_region b,

net_element d,

(select *

from tp_domain_listvalues

where domain_code LIKE 'DOMAIN_ALARM_STATE%') f,

kpi_code_list g,

ne_alarm_msg_source_rela h,

net_element k,

manage_region l

where a1.alarm_region_origin = b.region_id(+)

and a1.ne_id = d.ne_id

--and instr(d.path, :P_NE_ID, 1, 1) > 0

and a1.alarm_state = f.list_value

and a1.kpi_id = g.kpi_id

and a1.ne_alarm_list_id = h.ne_alarm_msg_id

and h.source_type = '19'

and h.source_id = k.ne_id

and k.region_id = l.region_id

and l.region_id in ('&PSOURCE_REGION')

order by a1.alarm_state, a1.create_time desc

截取部分执行计划分析,我们惊奇的发现,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个取值,都是大量的返回。因此我们要做的事情非常简单,重新收集这个表的列的直方图即可,如下:

exec dbms_stats.gather_table_stats(ownname => 'BOSSWG',tabname => 'NE_ALARM_MSG_SOURCE_RELA',estimate_percent => 10,method_opt=>'for all columns size 254',cascade=>TRUE) ;

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

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

3.NOT IN 与表连接优化(来自浙江环境)

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

SELECT T.FLOW_ID

FROM FLOW_HIS T

WHERE T.FLOW_MOD = 11263

AND T.FLOW_ID NOT IN (SELECT ZJ.FLOW_ID FROM ZJ_EVENT_HIS ZJ)

执行计划如下:

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

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

FROM FLOW_HIS T

WHERE T.FLOW_MOD = 11263

AND T.FLOW_ID NOT IN

(SELECT ZJ.FLOW_ID FROM ZJ_EVENT_HIS ZJ where flow_id is not null)

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

相关主题
相关文档
最新文档