1. 程式人生 > >分頁SQL優化之一

分頁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;