简单优化。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;
当然 基于这个方法可以去关联其他信息。
然后调查可疑地方的问题, 评估该步是否该走索引, 该走全表....
哥懒得一步步搞了。 毕竟SQl太复杂了。直接 A-rows E-rows 的值计划。
谓词信息不贴了和上面一样。
是不是这一步成本最高的??
* 28 | TABLE ACCESS FULL | PROD_REL | 695 | 1 | 197 |00:00:00.48 | 104K|
有人说全表扫描莫, 在该步弄一个index。 这样其实不好。弄清楚逻辑再说。对应id = 10 filter。 疑问 一般要么nested_loop 要莫hash. 这步为啥filter?
怎么还有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万。
大头继续优化。
还行基本上是预期了。 在执行计划中, 大头在Id = 20,21,22,23 中。这边一眼看到性能瓶颈。 nested_loop 性能瓶颈之一 于是换成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左右,得出结论这边走hash 性能最佳。 这一步优化好了再 添加第一次去掉的 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;