2011-02-15 36 views
3

我試圖運行Oracle 8i的服務器上的以下PL/SQL(老,我知道):的Oracle 8i的日期功能緩慢

select 
    -- stuff -- 
from 
    s_doc_quote d, 
    s_quote_item i, 
    s_contact c, 
    s_addr_per a, 
    cx_meter_info m 
where 
    d.row_id = i.sd_id 
    and d.con_per_id = c.row_id 
    and i.ship_per_addr_id = a.row_id(+) 
    and i.x_meter_info_id = m.row_id(+) 
    and d.x_move_type in ('Move In','Move Out','Move Out/Move In') 
    and i.prod_id in ('1-QH6','1-QH8') 
    and d.created between add_months(trunc(sysdate,'MM'), -1) and sysdate 
; 

執行是非常緩慢的但是。由於服務器每晚在午夜時分停機,因此通常無法及時完成。

執行計劃如下:

SELECT STATEMENT 1179377 
NESTED LOOPS 1179377 
    NESTED LOOPS OUTER 959695 
    NESTED LOOPS OUTER 740014 
    NESTED LOOPS 520332 
    INLIST ITERATOR 
     TABLE ACCESS BY INDEX ROWID S_QUOTE_ITEM 157132 
     INDEX RANGE SCAN S_QUOTE_ITEM_IDX8 8917 
    TABLE ACCESS BY INDEX ROWID S_DOC_QUOTE 1 
     INDEX UNIQUE SCAN S_DOC_QUOTE_P1 1 
    TABLE ACCESS BY INDEX ROWID S_ADDR_PER 1 
    INDEX UNIQUE SCAN S_ADDR_PER_P1 1 
    TABLE ACCESS BY INDEX ROWID CX_METER_INFO 1 
    INDEX UNIQUE SCAN CX_METER_INFO_P1 1 
    TABLE ACCESS BY INDEX ROWID S_CONTACT 1 
    INDEX UNIQUE SCAN S_CONTACT_P1 1 

如果我改變下列其中然而子句:

and d.created between add_months(trunc(sysdate,'MM'), -1) and sysdate 

要的靜態值,諸如:

and d.created between to_date('20110101','yyyymmdd') and sysdate 

的執行計劃變爲:

SELECT STATEMENT 5 
NESTED LOOPS 5 
    NESTED LOOPS OUTER 4 
    NESTED LOOPS OUTER 3 
    NESTED LOOPS 2 
    TABLE ACCESS BY INDEX ROWID S_DOC_QUOTE 1 
     INDEX RANGE SCAN S_DOC_QUOTE_IDX1 3 
    INLIST ITERATOR 
     TABLE ACCESS BY INDEX ROWID S_QUOTE_ITEM 1 
     INDEX RANGE SCAN S_QUOTE_ITEM_IDX4 4 
    TABLE ACCESS BY INDEX ROWID S_ADDR_PER 1 
    INDEX UNIQUE SCAN S_ADDR_PER_P1 1 
    TABLE ACCESS BY INDEX ROWID CX_METER_INFO 1 
    INDEX UNIQUE SCAN CX_METER_INFO_P1 1 
    TABLE ACCESS BY INDEX ROWID S_CONTACT 1 
    INDEX UNIQUE SCAN S_CONTACT_P1 1 

它幾乎立即開始返回行。

到目前爲止,我已經嘗試用綁定變量替換動態日期條件,以及使用從雙表中選擇動態日期的子查詢。迄今爲止,這些方法都沒有幫助提高性能。

因爲我對PL/SQL相對來說比較陌生,所以我無法理解執行計劃中這些實質性差異的原因。

我也試圖從SAS傳遞查詢作爲傳遞,但爲了測試執行速度的目的,我一直在使用SQL * Plus。

編輯:

爲了澄清,我已經使用綁定變量如下已經嘗試過:

var start_date varchar2(8); 
exec :start_date := to_char(add_months(trunc(sysdate,'MM'), -1),'yyyymmdd') 

用下面的where子句:

and d.created between to_date(:start_date,'yyyymmdd') and sysdate 

返回的執行成本1179377.

我也想避免綁定變量,如果po因爲我不相信我可以從SAS傳遞查詢中引用他們(儘管我可能是錯的)。

+0

這些表上的優化器統計是最新的?索引和外鍵等定義。從計劃中無法確定,但它可能無法爲輸出集計算正確的基數,然後選擇嵌套循環 – 2011-02-15 13:46:04

+0

@MikeyByCrikey - 查詢設置爲使用索引,儘管我沒有有不幸的是有權分析數據庫/表的統計數據。 – 2011-02-15 14:15:40

回答

8

我懷疑這裏的問題與ADD_MONTHS函數的執行時間有很大關係。您已經表明,在使用硬編碼的最短日期時,執行計劃中存在顯着差異。執行計劃中的巨大變化對運行時間的影響通常比函數調用開銷可能更大,但可能不同的執行計劃可能意味着函數被調用了更多次。無論哪種方式,要看的根本問題是爲什麼你沒有得到你想要的執行計劃。

良好的執行計劃從S_DOC_QUOTE_IDX1的範圍掃描開始。鑑於查詢變化的性質,我認爲這是CREATED列的索引。當篩選條件基於SYSDATE時,優化程序通常不會選擇在日期列上使用索引。因爲在執行時間之前不會評估它,所以在確定執行計劃之後,解析器無法對日期過濾條件的選擇性做出很好的估計。當您使用硬編碼的開始日期時,解析器可以使用該信息來確定選擇性,並對使用索引做出更好的選擇。

我也會提出綁定變量,但我認爲,因爲你在8i上,優化器不能偷看綁定值,所以這使得它在黑暗中與以前一樣多。在後來的Oracle版本中,我期望綁定解決方案會有效。因爲(a)開始日期值不是用戶指定的,並且(b)它對於整體而言將保持不變,但是,使用文字替換可能比使用綁定變量更合適,因爲(a)開始日期值不是用戶指定的,月,所以你不會解析很多稍微不同的查詢。

所以我的建議是編寫一些代碼來確定開始日期的靜態值,並在解析&執行之前將其直接連接到查詢字符串中。

1

這是因爲每個比較都運行該函數。

有時它的速度更快把它放在一個選擇從雙:

and d.created 
    between (select add_months(trunc(sysdate,'MM'), -1) from dual) 
    and sysdate 

否則,你也可以加入這樣的日期:

select 
    -- stuff -- 
from 
    s_doc_quote d, 
    s_quote_item i, 
    s_contact c, 
    s_addr_per a, 
    cx_meter_info m, 
    (select add_months(trunc(sysdate,'MM'), -1) as startdate from dual) sd 
where 
    d.row_id = i.sd_id 
    and d.con_per_id = c.row_id 
    and i.ship_per_addr_id = a.row_id(+) 
    and i.x_meter_info_id = m.row_id(+) 
    and d.x_move_type in ('Move In','Move Out','Move Out/Move In') 
    and i.prod_id in ('1-QH6','1-QH8') 
    and d.created between sd.startdate and sysdate 

最後選項,實際上是提高了最好的機會性能:爲此查詢添加日期參數,如下所示:

and d.created between :startdate and sysdate 

對不起,我看到你已經嘗試過這些選項。仍然很奇怪。如果常數值有效,那麼只要將add_months函數保留在查詢之外,綁定參數也應該正常工作。

+0

但是,我已經嘗試過類似於您的建議。然而,我懷疑其中的原因是你要提到多次調用這個函數。 – 2011-02-15 13:20:49

+0

當您使用綁定參數或加入選擇(建議3和2響應)時,不應發生這種情況。不過,Oracle以神祕的方式工作。 :) – GolezTrol 2011-02-15 13:50:31

0

這是SQL。您可能想要使用PL/SQL並將計算add_months(trunc(sysdate,'MM'),-1)保存到變量中,然後將其綁定。

此外,我看到SAS計算需要很長時間,因爲它需要通過網絡提取數據,並在其處理的每一行上執行額外的工作。根據你的環境,你可以考慮創建一個臨時表來存儲這些連接的結果,然後點擊臨時表(嘗試一個CTAS)。

+0

但是,我已經嘗試過綁定變量。關於使用SAS,我確保通過傳遞查詢完成所有操作,以便只返回結果並且不在SAS端進行處理。 – 2011-02-15 13:22:03

6

首先,您得到不同執行時間的原因並不是因爲Oracle執行了很多日期函數。這個SQL函數的執行,即使是針對每一行完成的(它可能不是這樣),與從磁盤/內存中實際檢索行所需的時間相比,所花費的時間只是微不足道的。

由於您已經注意到,Oracle正在獲得完全不同的執行時間,因此Oracle選擇了不同的訪問路徑。選擇一條訪問路徑可能會導致執行時間差異的大小順序。因此,真正的問題不是「爲什麼add_months需要時間?」但是:

爲什麼Oracle選擇這種特殊的低效路徑,而效率更高?

要回答這個問題,必須瞭解優化器的工作原理。 optimizer通過估計幾條訪問路徑(如果只有少數幾個表)的成本並選擇預期最有效的執行計劃來選擇特定的訪問路徑。確定執行計劃成本的算法具有規則,並根據從您的數據收集的統計信息進行估計。

作爲所有的估計算法,它會對您的數據進行假設,例如基於列的最小/最大值,基數以及段中值的物理分佈(聚類因子)的一般分佈。

如何適用於您的特定查詢

在你的情況下,優化已做出不同的過濾條款的選擇性的估計。在第一個查詢中,過濾器介於兩個變量之間(add_months(trunc(sysdate,'MM'), -1) and sysdate),而在另一種情況下,過濾器介於常量和變量之間。

它們看起來和你一樣,因爲你已經用變量的值代替了變量,但是對於優化器,情況有很大的不同:優化器(至少在8i中)只針對特定查詢計算一次執行計劃。一旦確定了訪問路徑,所有進一步的執行都將得到相同的執行計劃。因此,它不能用其值替換變量,因爲該值可能在將來發生變化,並且訪問計劃必須適用於所有可能的值。

由於第二個查詢使用變量,因此優化程序無法準確確定第一個查詢的選擇性,因此優化程序會進行猜測,並導致您的情況出現錯誤的計劃。

你能做些什麼時,優化器沒有選擇正確的計劃

如上mentionned,優化有時會讓壞的猜測,導致次優的訪問路徑。即使它很少發生,這可能是災難性的(幾小時而不是幾秒鐘)。這裏有一些動作,你可以嘗試:

  • 確保您的統計均達到最新ALL_TABLESALL_INDEXES上的last_analyzed列會告訴您上次收集這些對象的統計信息的時間。良好的可靠統計導致更準確的猜測,帶領(希望)更好的執行計劃。
  • 瞭解收集統計信息的不同選項(dbms_stats程序包)
  • 重寫查詢以在有意義時使用常量,以便優化程序進行更可靠的猜測。
  • 有時,兩個邏輯相同的查詢會導致不同的執行計劃,因爲優化器不會計算相同的訪問路徑(所有可能的路徑)。
  • 有一些技巧,你可以用它來強制優化執行別人之前的一些加盟,例如:
    • 使用ROWNUM來materialize a subquery(可能需要更多的臨時空間,但將允許您通過強制優化一個特定的步驟)。
    • 使用hints,雖然大部分時間我只會在所有其他的失敗時轉向提示。特別是,我有時使用LEADING提示強制優化器從特定的表格(或幾個表格)開始。
  • 最後,您可能會發現更新的版本具有通常更可靠的優化器。8I是12+歲,可以進行升級:)

這真是一個有趣的話題是時間。 Oracle優化器是不斷變化的(在版本之間),隨着時間的推移它會得到改進,即使在缺陷得到糾正時有時會引入新的怪癖。如果你想了解更多,我會建議喬納森劉易斯'Cost Based Oracle: Fundamentals