2011-12-20 61 views
1

我正在處理一個數據倉庫項目,因此,我一直在實現一些ETL函數的包。我在開發筆記本電腦時遇到了一個問題,並認爲它與我的oracle安裝有關,但現在它已經「傳播」到生產服務器。 兩個功能「有時」變得難以置信的緩慢。我們已經實現了一個日誌記錄系統,使我們在每個x行的日誌記錄表上輸出結果。當功能通常需要每塊大約10秒時,「有時」功能需要長達3分鐘。在重建一些索引並重新啓動該功能之後,它和以前一樣快。 不幸的是,我無法確切知道它是哪個索引,因爲重新啓動函數並構建它用於其工作的光標需要一些時間,我們沒有時間自行檢查每個索引,所以我只是重建所有可能被該函數使用的索引並重新啓動它。Oracle索引「打破」

出現問題的函數使用遊標從表中選擇約5000萬到200萬個條目中的數據,並通過一個包含大約50-500個條目的小表進行連接。連接條件是字符串比較。然後,我們使用從聯接獲得的小表中的主鍵更新主表上的外鍵。更新過程由循環完成,這已經證明可以節省大量時間。

下面是兩個表的表結構的簡化版本:

CREATE TABLE "maintable" 
( "pkmid" NUMBER(11,0) NOT NULL ENABLE, 
"fkid" NUMBER(11,0), 
"fkstring" NVARCHAR2(4) NOT NULL ENABLE, 
CONSTRAINT "PK_MAINTABLE" PRIMARY KEY ("pkmid"); 

CREATE TABLE "smalltable" 
( "pksid" NUMBER(11,0) NOT NULL ENABLE, 
"pkstring" NVARCHAR2(4) NOT NULL ENABLE, 
CONSTRAINT "PK_SMALLTABLE" PRIMARY KEY ("pksid"); 

兩個表都有自己的字符串列索引。添加主鍵時,我會在每次發生問題時重建4個索引。

我們以某種方式得到我們的數據,我們只有可用維護中的fkstring和fkid設置爲null。在第一步中,我們填充小表。這隻需要幾分鐘,並按以下方式完成:

INSERT INTO smalltable (pksid, pkstring) 
    SELECT SEQ_SMALLTABLE.NEXTVAL, fkstring 
     FROM 
     (
      SELECT DISTINCT mt.fkstring 
       FROM maintable mt       
      MINUS 
      SELECT st.pkstring 
       FROM smalltable st 
     ); 
     commit; 

此功能從不會引起任何問題。

下面的函數(它是功能的簡化版本 - 我已刪除的日誌記錄和異常處理,並改名一些變量):

function f_set_fkid return varchar2 is 

    cursor lCursor_MAINTABLE is 
     SELECT MT.PKmID, st.pksid 
      FROM maintable mt 
      JOIN smalltable st ON (mt.fkstring = st.pkstring) 
      WHERE mt.fkid IS NULL; 
    lIndex number := 0; 
    lExitLoop boolean := false; 

    type lCursorType is table of lCursor_MAINTABLE%rowtype index by pls_integer; 
    lCurrentRow lCursor_MAINTABLE%rowtype; 
    lTempDataArray lCursorType; 
    lCommitEvery constant number := 1000; 

    begin 

     open lCursor_MAINTABLE; 
      loop 

       -- get next row, set exit condition 
       fetch lCursor_MAINTABLE into lCurrentRow; 
       if (lCursor_MAINTABLE%notfound) then 
        lExitLoop := true; 
       end if; 

       -- in case of cache being full, flush cache 
       if ((lTempDataArray.count > 0) AND (lIndex >= lCommitEvery OR lExitLoop)) then 
        forall lIndex2 in lTempDataArray.FIRST..lTempDataArray.LAST 
         UPDATE maintable mt 
          set fkid = lTempDataArray(lIndex2).pksid 
          WHERE mt.pkmid = lTempDataArray(lIndex2).pkmid;  
        commit; 
        lTempDataArray.delete; 
        lIndex := 0;       
       end if;             

       -- data handling, fill cache 
       if (lExitLoop = false) then 
        lIndex := lIndex + 1;         
        lTempDataArray(lIndex). := lCurrentRow; 
       end if; 

       exit when lExitLoop; 

      end loop; 

     close lCursor_MAINTABLE; 

     return null; 

    end; 

我將是任何幫助非常感謝。

P.S.我知道批量收集可以加快函數的速度,也可能稍微緩解代碼,但目前我們滿足於通常所具有的函數的速度。改變使用批量收集的功能在我們明年的計劃中,但目前它不是一個選項(我懷疑它會解決這個索引問題)。

+1

我一直在看這段代碼一段時間,但它對我來說沒有任何意義。出現很多問題。我有強烈的印象,這個設置需要重新設計。可能把所有的邏輯放在一個更新語句中。爲什麼在返回null時使用函數?首先清理邏輯,然後開始優化性能。 – 2011-12-20 11:43:57

+0

簡單更新語句的數據太多。嘗試在單個語句中更新大型表上的200萬行,然後運行到表空間溢出。至於函數而不返回任何東西,請忽略它。我們使用這個從函數獲得的一些反饋返回到調用函數,但是我已經從這個示例中刪除了它。現在這個函數在我們的數據上運行了好幾個星期,除了索引被破壞之外。 – 2011-12-20 12:32:44

+2

更新200 miljoen行不是太多。但我強烈建議你清理代碼的程序流程。我無法獲得使用lTempDataArray背後的邏輯 – 2011-12-20 14:09:08

回答

2

如果你有一個表,其中的行數波動很大(因爲這樣做ETL負載時)我會用整個裝載過程滿載表的統計信息。

因此,當您的表完全加載時生成統計信息,然後將這些統計信息用於後續加載。

如果您使用統計數據時,表格是半載的,那麼優化程序可能會陷入不使用索引或不使用最快索引。如果數據按順序加載以使低值,高值和高密度偏斜,則尤其如此。

對於您的情況,fkstringfkid列的統計數據非常重要,因爲這兩列大量涉及具有性能問題的過程。

+1

另一個方便的方法是刪除所有的統計數據並依靠動態採樣。默認級別不太積極,通常做得非常好。 – 2013-06-17 11:48:55

0
function f_set_fkid return varchar2 is 

     cursor lCursor_MAINTABLE is 
      SELECT MT.PKmID, st.pksid 
       FROM maintable mt 
       JOIN smalltable st ON (mt.fkstring = st.pkstring) 
       WHERE mt.fkid IS NULL; 
     commit_every INTGER := 1000000; 
     commit_counter INTEGER :=0;  
begin 
    for c in lCursor_MAINTABLE 
    loop 
     UPDATE maintable mt 
     set fkid = c.pksid 
     WHERE mt.pkmid = c.pkmid;  
     commit_counter := commit_counter+1; 
     if mod(commit_every,commit_counter) = 0 
     then 
     commit; 
     commit_counter := 0; 
     end if; 
    end loop; 
    return null; 
end;