分頁SQL優化之一
簡單優化。SQL_ID:ads09ymdgr597 業務高峰期 21萬邏輯讀/次 業務高峰期邏輯讀TOP1
該次優化發現在資料庫中一個SQL多個執行計劃,是否有特別低效的拉大平均值? 單次21萬其實不算太多。
select * from table(dbms_xplan.display('ads09ymdgr597',null)); 就一個執行計劃。 那證明就搞這個得了。
話不多說:SQL(帶入繫結變數後的SQL)
SELECT *
FROM (SELECT XX.*, ROWNUM AS RN
FROM (select o.OFFER_ID,
o.OFFER_NAME,
o.OFFER_TYPE,
o.OFFER_NBR,
o.OFFER_SYS_TYPE,
o.OFFER_SYS_NAME,
o.OFFER_SYS_PY_NAME,
o.OFFER_DESC,
o.EFF_DATE,
o.MANAGE_GRADE,
o.EXP_DATE,
o.OFFER_PROVIDER_ID,
o.BRAND_ID,
o.VALUE_ADDED_FLAG,
o.INITIAL_CRED_VALUE,
o.PRICING_PLAN_ID,
o.IS_INDEPENDENT,
o.MANAGE_REGION_ID,
cr.REGION_NAME,
o.STATUS_CD,
a.ORDER_TIMES_RULE_ID as A_ORDER_TIMES_RULE_ID,
a.OFFER_ID as A_OFFER_ID,
a.OBJ_TYPE as A_OBJ_TYPE,
a.ORDER_TIMES as A_ORDER_TIMES,
a.TIME_TYPE as A_TIME_TYPE,
a.LIMIT_TIME as A_LIMIT_TIME,
a.TIME_UNIT as A_TIME_UNIT,
a.EFF_DATE as A_EFF_DATE,
a.EXP_DATE as A_EXP_DATE,
a.ORDER_TYPE as A_ORDER_TYPE,
a.APPLY_REGION_ID as A_APPLY_REGION_ID,
a.STATUS_CD as A_STATUS_CD,
a.CREATE_STAFF as A_CREATE_STAFF,
a.UPDATE_STAFF as A_UPDATE_STAFF,
a.STATUS_DATE as A_STATUS_DATE,
a.CREATE_DATE as A_CREATE_DATE,
a.UPDATE_DATE as A_UPDATE_DATE,
a.REMARK as A_REMARK,
a.OFFER_PROD_REL_ID as A_OFFER_PROD_REL_ID
from SPEC_APP_HA.OFFER o
left join SPEC_APP_HA.COMMON_REGION cr
on cr.COMMON_REGION_ID = o.MANAGE_REGION_ID
left join SPEC_APP_HA.OFFER_ORDER_TIMES_RULE a
on a.offer_id = o.offer_id
where not exists (select 1
from SPEC_APP_HA.offer_ext_attr ext
where ext.attr_id = '300000007'
and ext.offer_id = o.offer_id
and ext.STATUS_CD = '1000')
and not exists
(select 1
from SPEC_APP_HA.PRODUCT p
where p.BASE_OFFER_ID = o.OFFER_ID)
and o.OFFER_TYPE != '10'
and o.OFFER_ID in
(select distinct c.offer_id
from SPEC_APP_HA.offer_grp_member o
left join SPEC_APP_HA.offer_prod_rel a
on a.offer_id = o.offer_id
left join SPEC_APP_HA.product b
on b.prod_id = a.prod_id
left join SPEC_APP_HA.offer c
on c.offer_id = o.offer_id
where o.offer_grp_id in
(select z_offer_grp_id
from SPEC_APP_HA.offer_rel
where a_offer_grp_id in
(select distinct offer_grp_id
from SPEC_APP_HA.offer_grp_member
where obj_type = '110000'
and status_cd = '1000'
and offer_id in (300500003485, 300500003485, 911008800, 911008801)
and APPLY_REGION_ID in
(8320826, 8320800, 8320000, -9999))
and rel_type = '700000'
and status_cd = '1000'
and APPLY_REGION_ID in (8320826, 8320800, 8320000, -9999))
and (b.prod_id in
(select x.z_prod_id
from SPEC_APP_HA.prod_rel x
where x.rel_type = '100600'
and x.a_prod_id = 100000379
and x.status_cd = '1000'
and x.APPLY_REGION_ID in
(8320826, 8320800, 8320000, -9999))
or
b.prod_id in
(select pcpr.prod_id
from SPEC_APP_HA.prod_comp_prod_rel pcpr
left join SPEC_APP_HA.prod_rel pr
on pr.prod_rel_id = pcpr.prod_rel_id
where pcpr.rel_type != '1400'
and pr.z_prod_id = 100000379
and pr.rel_type = '100500'
and pr.status_cd = '1000'
and pcpr.status_cd = '1000'
and pr.APPLY_REGION_ID in
(8320826, 8320800, 8320000, -9999))
or b.prod_id = 100000379)
and c.EFF_DATE <= sysdate
and c.EXP_DATE >= sysdate
and c.status_cd = '1000'
and b.status_cd = '1000'
and a.status_cd = '1000'
and o.status_cd = '1000'
and o.APPLY_REGION_ID in (8320826, 8320800, 8320000, -9999))
and o.OFFER_TYPE = 13
and o.MANAGE_REGION_ID in (8320826, 8320800, 8320000, -9999)) XX
WHERE ROWNUM <= 10) XXX
WHERE RN > 0;
一開始看還真沒有看到可疑的地方。
於是看執行計劃。
看到可疑的地方,當時找的可疑的地方。
|* 18 | TABLE ACCESS BY INDEX ROWID| OFFER_REL | 1 | 21 | 2 (0)| 00:00:01 |
|* 19 | INDEX FULL SCAN | IDX_OFFER_REL_1 |
|* 29 | TABLE ACCESS FULL | PROD_REL
|* 32 | TABLE ACCESS FULL | PROD_COMP_PROD_REL |
|* 43 | TABLE ACCESS FULL | PRODUCT |
|* 10 | FILTER | | | | | |
|* 11 | FILTER
看下錶大小。最大的表 OFFER_GRP_MEMBER 16M。 OFFER 8M 反正都是小表。這個不說了。
有些人說怎麼多表看起來麻煩, 優化時候估計會偷懶, 其實有簡單方法。 我都是用這個方法的
select segment_name, max(se.owner) owner, sum(bytes/1024/1024) tab_SIZE_MB, decode(max(partition_name),null,'NO','YES') tabpartitioned
from dba_segments se
where se.segment_type like 'TABLE%'
and (se.segment_name,se.owner) in ( select t.object_name,t.object_owner from v$sql_plan t where sql_id = 'ads09ymdgr597' )
group by se.segment_name ,se.owner;
當然 基於這個方法可以去關聯其他資訊。
謂詞資訊不貼了和上面一樣。
是不是這一步成本最高的??
* 28 | TABLE ACCESS FULL | PROD_REL | 695 | 1 | 197 |00:00:00.48 | 104K|
有人說全表掃描莫, 在該步弄一個index。 這樣其實不好。弄清楚邏輯再說。該步 id = 10 filter。 疑問 要麼nested_loop 要莫hash.
怎麼還有filter?? 在SQL中尋找答案。 對應的SQl條件 and (b.prod_id in ( ) or b.prod_id in ( ) or b.prod_id = 100000379)。 這?? 這!這 不無語。難怪會filter。
哥乾脆把這個條件去掉看看效率再說。
Predicate Information (identified by operation id):
--- 謂詞資訊就不貼了。。
去掉 and (b.prod_id in ( ) or b.prod_id in ( ) or b.prod_id = 100000379) 後邏輯讀減少到9萬。
去掉後還是很多效果的21萬減少到9萬。繼續優化。
還行基本上是預期了。 在新秩序計劃中, 大頭在Id = 20,21,22,23 中。這邊一眼看到效能瓶頸。 nested_loop 效能瓶頸之一, 驅動表rows 太多。 於是換成hash
試試。 哥把效能大頭地方對應的SQL剝離開來,得到SQL:
select distinct c.offer_id from SPEC_APP_HA.offer_grp_member o
left join SPEC_APP_HA.offer_prod_rel a on a.offer_id = o.offer_id
left join SPEC_APP_HA.product b on b.prod_id = a.prod_id
left join SPEC_APP_HA.offer c on c.offer_id = o.offer_id
where o.offer_grp_id in
(select z_offer_grp_id from SPEC_APP_HA.offer_rel
where a_offer_grp_id in
(select distinct offer_grp_id from SPEC_APP_HA.offer_grp_member
where obj_type = '110000' and status_cd = '1000'
and offer_id in (300500003485, 300500003485, 911008800, 911008801)
and APPLY_REGION_ID in (8320826, 8320800, 8320000, -9999))
and rel_type = '700000' and status_cd = '1000'
and APPLY_REGION_ID in (8320826, 8320800, 8320000, -9999))
and c.EFF_DATE <= sysdate and c.EXP_DATE >= sysdate
and c.status_cd = '1000' and b.status_cd = '1000' and a.status_cd = '1000' and o.status_cd = '1000'
and o.APPLY_REGION_ID in (8320826, 8320800, 8320000, -9999);
優化這個SQL,由於篇幅問題,而且執行計劃和上面對應的地方的執行計劃一樣的。
分兩次執行 第一次原版執行, 第二次新增hints /*+ use_hash(o,c) use_hash(o,a) use_hash(b,a) */
原版:
統計資訊
----------------------------------------------------------
1 recursive calls
0 db block gets
89483 consistent gets
0 physical reads
0 redo size
.....
1501 rows processed
看到8.9萬邏輯讀我當時是非常高興的, 不知道有沒有讀者注意到上面說的一句:
and (b.prod_id in ( ) or b.prod_id in ( ) or b.prod_id = 100000379) 後邏輯讀減少到9萬。
那證明再一次找到效能瓶頸的大頭。這個如果能優化。那肯定效果很不多啊。
新增hints後:
統計資訊
----------------------------------------------------------
1 recursive calls
0 db block gets
3669 consistent gets
0 physical reads
0 redo size
....
0 sorts (disk)
1501 rows processed
這個小SQL優化好了後融入大SQL中, 邏輯讀只有4000多。
這一步優化好了再 新增第一次去掉的 in or in 部分
and (b.prod_id in ( ) or b.prod_id in ( ) or b.prod_id = 100000379) 這種SQl的改寫方案
改成 and (b.prod_id in ( union all )):
SQL:SQL 就不貼全了,太長了。
SELECT *
FROM (SELECT XX.*, ROWNUM AS RN
FROM (select o.OFFER_ID, ....
a.OFFER_PROD_REL_ID as A_OFFER_PROD_REL_ID
from SPEC_APP_HA.OFFER o
left join SPEC_APP_HA.COMMON_REGION cr
on cr.COMMON_REGION_ID = o.MANAGE_REGION_ID
left join SPEC_APP_HA.OFFER_ORDER_TIMES_RULE a
on a.offer_id = o.offer_id
where ....
and o.OFFER_ID in
(select /*+ use_hash(o,c) use_hash(o,a) use_hash(b,a) */ c.offer_id ... )
and b.prod_id in
(select x.z_prod_id from SPEC_APP_HA.prod_rel x ...
union all
select pcpr.prod_id from SPEC_APP_HA.prod_comp_prod_rel pcpr
union all
select 100000379 from dual
)
... ) XX
WHERE ROWNUM <= 10) XXX
WHERE RN > 0;
還是6.8萬邏輯讀。。。。
這邊就不說了(寫兩句吃飯去了), 如果你不能一眼看出效能瓶頸 那你繼續修煉吧。
於是新增hint
SELECT *
FROM (SELECT XX.*, ROWNUM AS RN
FROM (select o.OFFER_ID, ....
a.OFFER_PROD_REL_ID as A_OFFER_PROD_REL_ID
from SPEC_APP_HA.OFFER o
left join SPEC_APP_HA.COMMON_REGION cr
on cr.COMMON_REGION_ID = o.MANAGE_REGION_ID
left join SPEC_APP_HA.OFFER_ORDER_TIMES_RULE a
on a.offer_id = o.offer_id
where ....
and o.OFFER_ID in
(select /*+ use_hash(o,c) use_hash(o,a) use_hash(b,a) */ c.offer_id ... )
and b.prod_id in
(select /*+ unnest */ x.z_prod_id from SPEC_APP_HA.prod_rel x ...
union all
select pcpr.prod_id from SPEC_APP_HA.prod_comp_prod_rel pcpr
union all
select 100000379 from dual
)
... ) XX
WHERE ROWNUM <= 10) XXX
WHERE RN > 0;
執行:
總結: 21萬邏輯讀。 優化到 6萬,再優化到5千邏輯讀。
還有可以優化的空間,讀者朋友有興趣的可以繼續看看。
SQL:
SELECT *
FROM (SELECT XX.*, ROWNUM AS RN
FROM (select o.OFFER_ID, ........ a.OFFER_PROD_REL_ID as A_OFFER_PROD_REL_ID
from SPEC_APP_HA.OFFER o
left join SPEC_APP_HA.COMMON_REGION cr on cr.COMMON_REGION_ID = o.MANAGE_REGION_ID
left join SPEC_APP_HA.OFFER_ORDER_TIMES_RULE a on a.offer_id = o.offer_id
where not exists (select 1 from SPEC_APP_HA.offer_ext_attr ext
where ext.attr_id = '300000007' and ext.offer_id = o.offer_id and ext.STATUS_CD = '1000')
and not exists
(select 1 from SPEC_APP_HA.PRODUCT p
where p.BASE_OFFER_ID = o.OFFER_ID)
and o.OFFER_TYPE != '10'
and o.OFFER_ID in
(select /*+ use_hash(o,c) use_hash(o,a) use_hash(b,a) */ c.offer_id
from SPEC_APP_HA.offer_grp_member o
left join SPEC_APP_HA.offer_prod_rel a
on a.offer_id = o.offer_id
left join SPEC_APP_HA.product b
on b.prod_id = a.prod_id
left join SPEC_APP_HA.offer c
on c.offer_id = o.offer_id
where o.offer_grp_id in
(select z_offer_grp_id
from SPEC_APP_HA.offer_rel
where a_offer_grp_id in
(select distinct offer_grp_id
from SPEC_APP_HA.offer_grp_member
where obj_type = '110000'
and status_cd = '1000'
and offer_id in (300500003485, 300500003485, 911008800, 911008801)
and APPLY_REGION_ID in
(8320826, 8320800, 8320000, -9999))
and rel_type = '700000'
and status_cd = '1000'
and APPLY_REGION_ID in (8320826, 8320800, 8320000, -9999))
and b.prod_id in
(select /*+ unnest */ x.z_prod_id
from SPEC_APP_HA.prod_rel x
where x.rel_type = '100600'
and x.a_prod_id = 100000379
and x.status_cd = '1000'
and x.APPLY_REGION_ID in (8320826, 8320800, 8320000, -9999)
union all
select pcpr.prod_id
from SPEC_APP_HA.prod_comp_prod_rel pcpr
left join SPEC_APP_HA.prod_rel pr
on pr.prod_rel_id = pcpr.prod_rel_id
where pcpr.rel_type != '1400'
and pr.z_prod_id = 100000379
and pr.rel_type = '100500'
and pr.status_cd = '1000'
and pcpr.status_cd = '1000'
and pr.APPLY_REGION_ID in
(8320826, 8320800, 8320000, -9999)
union all
select 100000379 from dual
)
and c.EFF_DATE <= sysdate
and c.EXP_DATE >= sysdate
and c.status_cd = '1000'
and b.status_cd = '1000'
and a.status_cd = '1000'
and o.status_cd = '1000'
and o.APPLY_REGION_ID in (8320826, 8320800, 8320000, -9999))
and o.OFFER_TYPE = 13
and o.MANAGE_REGION_ID in (8320826, 8320800, 8320000, -9999)) XX
WHERE ROWNUM <= 10) XXX
WHERE RN > 0;