1. 程式人生 > >帶有自定義函式的sql優化

帶有自定義函式的sql優化

author:skate
time:2009/05/14


一. 第一個sql
 
 Buffer Gets    Executions  Gets per Exec  %Total Time (s)  Time (s) Hash Value
--------------- ------------ -------------- ------ -------- --------- ----------
     60,414,728          195      309,819.1   28.1   619.83  11991.55  905212684

優化前:

SELECT
TOM.ORDER_ID
  FROM TB_ORDER_MATCH TOM
  LEFT JOIN TB_ORDER TOD ON TOD.ID = TOM.ORDER_ID
 WHERE TOD.WARE_ID = 32135
   AND TOM.GAME_NO = 245
   AND TOD.BUY_QUOMODO = 1
   AND TOD.STATE = 0
   AND TOD.IS_ADVANCED = 'Y'
 ORDER BY TOM.ORDER_ID

優化後:


 SELECT /*+ index(tod,PIDX_ORDER_WARE_ID)*/
TOM.ORDER_ID
  FROM TB_ORDER_MATCH TOM
  LEFT JOIN TB_ORDER TOD ON TOD.ID = TOM.ORDER_ID
 WHERE TOD.WARE_ID = 32135
   AND TOM.GAME_NO = 245
   AND TOD.BUY_QUOMODO = 1
   AND TOD.STATE = 0
   AND TOD.IS_ADVANCED = 'Y'
 ORDER BY TOM.ORDER_ID
          
這個sql特點是:執行的次數不是很少,每個sql執行非常耗邏輯讀(309,819.1)  

優化前後對比:


優化前執行時間:1.6秒左右
優化後執行時間:0.001秒

請把以上這個sql放到相應位置          
          

2.第二,三個sql 

如下的優化過程已經優化的生產庫

優化前後對比:

優化前執行時間:1.6秒左右

優化後執行時間:0.001

速度提高近1600


           CPU      Elapsd
  Buffer Gets    Executions  Gets per Exec  %Total Time (s)  Time (s) Hash Value
--------------- ------------ -------------- ------ -------- --------- ----------
     35,382,746           20    1,769,137.3   16.4   132.72    132.41  390687896

 SELECT
        w.ID AS ware_id,
        l.ID as lottery_id,
        l.lottery_name AS lottery_name,
        w.issue AS ware_issue,
        (f_recall_earliest_gametime(w.ID) + f_recall_advanced_by_gametime()) AS start_time
 FROM tb_lottery l,tb_ware w

 WHERE  l.ID = w.lottery_id AND l.sort_name <> 2 AND l.id <> 57 AND l.allow_advanced = 'Y'
 AND w.state IN (1,-1)
 AND NOT EXISTS
     (
       SELECT * FROM tb_recall_task rt
       WHERE rt.recall_type = 32 AND rt.ware_id = w.ID AND rt.game_no = 0 AND rt.is_done = 'Y'
     )
 AND (f_recall_earliest_gametime(w.ID) + f_recall_advanced_by_gametime()) <= to_date('2009-06-03 00:00:00','yyyy-mm-dd hh24:mi:ss')
ORDER BY  ware_id


這個語句的特點是雖然執行次數很少,僅有20次,但是他每次執行卻消耗很多資源,讀取buffer:1,769,137.3  其實超過100000的就說明這個sql的效能已經很差了

這也sql在優化前執行速度:7.83秒

狀態:
TB_WARE: 14191
TB_LOTTERY; 58
TB_RECALL_TASK; 249276

這個sql返回很少的行,本sql返回8行記錄


分析從這語句中執行計劃中可以看到,執行計劃很好,但就為什麼還慢呢,我於是採用試探法

1.把語句簡化為:

 SELECT
        w.ID AS ware_id,
        l.ID as lottery_id,
        l.lottery_name AS lottery_name,
        w.issue AS ware_issue,
        (f_recall_earliest_gametime(w.ID) + f_recall_advanced_by_gametime()) AS start_time
 FROM tb_lottery l,tb_ware w

 WHERE  l.ID = w.lottery_id AND l.sort_name <> 2 AND l.id <> 57 AND l.allow_advanced = 'Y'
 AND w.state IN (1,-1)
 /*AND NOT EXISTS
     (
       SELECT * FROM tb_recall_task rt
       WHERE rt.recall_type = 32 AND rt.ware_id = w.ID AND rt.game_no = 0 AND rt.is_done = 'Y'
     )*/
 AND (f_recall_earliest_gametime(w.ID) + f_recall_advanced_by_gametime()) <= to_date('2009-06-03 00:00:00','yyyy-mm-dd hh24:mi:ss')
ORDER BY  ware_id;

但sql的執行效能幾乎沒有變化,

2.猜想可能是因為那兩個函式的原因

f_recall_earliest_gametime(w.ID)
f_recall_advanced_by_gametime()


sql進一步簡化:

 SELECT
        w.ID AS ware_id,
        l.ID as lottery_id,
        l.lottery_name AS lottery_name,
        w.issue AS ware_issue,
       -- (f_recall_earliest_gametime(w.ID) + f_recall_advanced_by_gametime()) AS start_time
 FROM tb_lottery l,tb_ware w

 WHERE  l.ID = w.lottery_id AND l.sort_name <> 2 AND l.id <> 57 AND l.allow_advanced = 'Y'
 AND w.state IN (1,-1)
 /*AND NOT EXISTS
     (
       SELECT * FROM tb_recall_task rt
       WHERE rt.recall_type = 32 AND rt.ware_id = w.ID AND rt.game_no = 0 AND rt.is_done = 'Y'
     )*/
-- AND (f_recall_earliest_gametime(w.ID) + f_recall_advanced_by_gametime()) <= to_date('2009-06-03 00:00:00','yyyy-mm-dd hh24:mi:ss')
ORDER BY  ware_id;

這次在查詢速度就發生了根本的變化,從7.83秒降低到 0.032秒,速度提高近244倍

盡然已經知道問題的原因,那就檢視那兩個函式

f_recall_earliest_gametime(w.ID)
f_recall_advanced_by_gametime()


以下兩個函式內容都很簡單,其中f_recall_advanced_by_gametime()就是返回常數值,這個實在是沒辦法優化了


f_recall_earliest_gametime(w.ID)中就一個語句,如下:

SELECT
MIN(G.GAME_TIME)
  FROM TB_GAME G
 INNER JOIN TB_MATCH M ON M.GAME_ID = G.ID
 WHERE M.WARE_ID = 20934

 觀察這個語句的執行計劃,發現這個sql佔buffer :45331 ,對這個sql優化


tb_game  44640行
tb_match 146764行

這個sql,返回很少的記錄行,但卻佔有那麼多的邏輯讀,一般情況是沒走正確的索引或走全表掃描

SQL> SELECT
  2  MIN(G.GAME_TIME)
  3    FROM TB_GAME G
  4   INNER JOIN TB_MATCH M ON M.GAME_ID = G.ID
  5   WHERE M.WARE_ID = 20934;

1 row selected.

Elapsed: 00:00:00.84

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=18 Card=1 Bytes=22)
   1    0   SORT (AGGREGATE)
   2    1     NESTED LOOPS (Cost=18 Card=43 Bytes=946)
   3    2       TABLE ACCESS (FULL) OF 'TB_GAME' (Cost=18 Card=44640 B
          ytes=580320)

   4    2       INDEX (UNIQUE SCAN) OF 'AK_TB_MATCHA' (UNIQUE)


Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
      45331  consistent gets
          0  physical reads
          0  redo size
        497  bytes sent via SQL*Net to client
        655  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

SQL>

調整之後的sql如下:

SELECT /*+ use_nl(g) index(g,PK_TB_GAME)*/
MIN(G.GAME_TIME)
  FROM TB_GAME G
 INNER JOIN TB_MATCH M ON M.GAME_ID = G.ID
 WHERE M.WARE_ID = 20934


 觀察其執行計劃如下:

 SQL> SELECT /*+ use_nl(g) index(g,PK_TB_GAME)*/
  2  MIN(G.GAME_TIME)
  3    FROM TB_GAME G
  4   INNER JOIN TB_MATCH M ON M.GAME_ID = G.ID
  5   WHERE M.WARE_ID = 20934;

1 row selected.

Elapsed: 00:00:00.60

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=45 Card=1 Bytes=22)
   1    0   SORT (AGGREGATE)
   2    1     NESTED LOOPS (Cost=45 Card=43 Bytes=946)
   3    2       INDEX (RANGE SCAN) OF 'AK_TB_MATCHA' (UNIQUE) (Cost=2
          Card=43 Bytes=387)

   4    2       TABLE ACCESS (BY INDEX ROWID) OF 'TB_GAME' (Cost=1 Car
          d=1 Bytes=13)

   5    4         INDEX (UNIQUE SCAN) OF 'PK_TB_GAME' (UNIQUE)


Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          2  consistent gets
          0  physical reads
          0  redo size
        497  bytes sent via SQL*Net to client
        655  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

這個兩個函式優化完了,再回到開始的那個sql,在執行時,發現其速度已經從7.83秒降到0.032秒


優化完畢,這兩個sql就是我前兩天抓取的,高邏輯讀的前三個sql

 -----end---