2012-10-01 51 views
2

我有2個刪除語句需要很長時間才能完成。 where子句中的列有幾個索引。優化dup刪除語句Oracle

什麼是重複? 如果2個或更多記錄在列id,cid,type,trefid或ordrefid,amount和paydt中具有相同的值,則會有重複項。

刪除約100萬條記錄。

它們可以以任何方式重寫以使其更快。

DELETE FROM TABLE1 A WHERE loaddt < (
    SELECT max(loaddt) FROM TABLE1 B 
    WHERE 
    a.id=b.id and 
    a.cid=b.cid and 
    NVL(a.type,'-99999') = NVL(b.type,'-99999') and 
    NVL(a.trefid,'-99999')=NVL(b.trefid,'-99999') and 
    NVL(a.ordrefid,'-99999')= NVL(b.ordrefid,'-99999') and 
    NVL(a.amount,'-99999')=NVL(b.amount,'-99999') and 
    NVL(a.paydt,TO_DATE('9999-12-31','YYYY-MM-DD'))=NVL(b.paydt,TO_DATE('9999-12-31','YYYY-MM-DD')) 
); 

    COMMIT; 

DELETE FROM TABLE1 a where rowid > (
    Select min(rowid) from TABLE1 b 
    WHERE 
    a.id=b.id and 
    a.cid=b.cid and 
    NVL(a.type,'-99999') = NVL(b.type,'-99999') and 
    NVL(a.trefid,'-99999')=NVL(b.trefid,'-99999') and 
    NVL(a.ordrefid,'-99999')= NVL(b.ordrefid,'-99999') and 
    NVL(a.amount,'-99999')=NVL(b.amount,'-99999') and 
    NVL(a.paydt,TO_DATE('9999-12-31','YYYY-MM-DD'))=NVL(b.paydt,TO_DATE('9999-12-31','YYYY-MM-DD')) 
); 

commit; 

解釋計劃:

DELETE TABLE1   

    HASH JOIN 1296491 
    Access Predicates 

     AND 
     A.ID=ITEM_1 
     A.CID=ITEM_2 
     ITEM_3=NVL(TYPE,'-99999') 
     ITEM_4=NVL(TREFID,'-99999') 
     ITEM_5=NVL(ORDREFID,'-99999') 
     ITEM_6=NVL(AMOUNT,(-99999)) 
     ITEM_7=NVL(PAYDT,TO_DATE(' 9999-12-31 00:00:00', 'syyyy-mm-dd hh24:mi:ss')) 

    Filter Predicates 
     LOADDT<MAX(LOADDT) 

    TABLE ACCESS TABLE1  FULL 267904 
    VIEW VW_SQ_1   690385 
    SORT GROUP BY 690385 
     TABLE ACCESS TABLE1  FULL 267904 
+0

究竟是什麼時間?你真正的問題是你不知道你在刪除過程中的位置(%完成)?另外,您在#2中的rowid的目標邏輯是什麼? – tbone

+0

長時間= 15小時。我真的想減少刪除重複項所需的時間。我們每週都會加載新數據並刪除前幾周的重複記錄,因此在#2中,如果條件(where子句)匹配但rowid較小,我們將嘗試刪除記錄。較低的rowid與前一週插入的記錄相關聯。希望我在這裏有道理。 – Ram

+0

什麼是重複?如果2列或更多記錄在列id,cid,type,trefid或ordrefid,amount和paydt中具有相同的值,則會有重複。 – Ram

回答

2

桌子有多大?如果刪除行的數量高達12%,那麼你可以考慮索引。 你能以某種方式分割你的表 - 像一週一週,然後只掃描實際的一週嗎?

也許這可能更有效。當你使用聚合函數時,oracle必須遍歷所有相關的行(在你的情況下是fullscan),但是當你使用exists時,它會在找到第一個事件時停止。 (當然的查詢會快很多,當時有一個(因爲NVL的基於功能的)在所有列的索引where子句)

DELETE FROM TABLE1 A 
WHERE exists (
SELECT 1 
FROM TABLE1 B 
WHERE 
A.loaddt != b.loaddt 
a.id=b.id and 
a.cid=b.cid and 
NVL(a.type,'-99999') = NVL(b.type,'-99999') and 
NVL(a.trefid,'-99999')=NVL(b.trefid,'-99999') and 
NVL(a.ordrefid,'-99999')= NVL(b.ordrefid,'-99999') and 
NVL(a.amount,'-99999')=NVL(b.amount,'-99999') and 
NVL(a.paydt,TO_DATE('9999-12-31','YYYY-MM-DD'))=NVL(b.paydt,TO_DATE('9999-12-31','YYYY-MM-DD')) 
); 
+0

+1提及分區。 (儘管我不確定從哪裏得到「12%」) –

+0

12%是基於我的經驗(來自行列大小和數據塊大小,例如我們有類似的表格),但它可以是1 %到100%。更多信息[here](http://richardfoote.wordpress.com/2008/05/12/index-scan-or-full-table-scan-the-magic-number-magic-dance/)索引可能沒有幫助。 (並且它會減慢刪除操作,所以它可能會惡化性能) –

+0

jakub DELETE刪除了超過12%的記錄。在查詢中也有A.loaddt!= b.loaddt,應該是A.loaddt = b.loaddt?對不起,你能解釋一下你在這個查詢中做什麼嗎? – Ram

1

雖然有些人可能不同意,我是運行大型的支持者,長時間運行的程序上刪除。在我看來,控制和跟蹤進度要容易得多(而且你的DBA會更喜歡你;-)另外,不確定爲什麼你需要將table1連接到它自己來識別重複項(如果你遇到過,我會好奇的快照與您當前的方法過時的問題)。你也不應該需要多個刪除語句,所有的重複應該在一個進程中處理。最後,你應該檢查爲什麼你每週都不斷地重複引入重複,並且可能改變加載過程(可能是做一個merge/upsert而不是所有的插入)。

這就是說,你可以嘗試這樣的:

-- first create mat view to find all duplicates 
create materialized view my_dups_mv 
tablespace my_tablespace 
build immediate 
refresh complete on demand 
as 
select id,cid,type,trefid,ordrefid,amount,paydt, count(1) as cnt 
from table1 
group by id,cid,type,trefid,ordrefid,amount,paydt 
having count(1) > 1; 

-- dedup data (or put into procedure and schedule along with mat view refresh above) 
declare 
    -- make sure my_dups_mv is refreshed first 
    cursor dup_cur is 
    select * from my_dups_mv; 

    type duprec_t is record(row_id rowid); 
    duprec duprec_t; 
    type duptab_t is table of duprec_t index by pls_integer; 
    duptab duptab_t; 

    l_ctr pls_integer := 0; 
    l_dupcnt pls_integer := 0; 
begin 
    for rec in dup_cur 
    loop 
    l_ctr := l_ctr + 1; 

    -- assuming needed indexes exist 
    select rowid 
    bulk collect into duptab 
    from table1 
    where id = rec.id 
    and cid = rec.cid 
    and type = rec.type 
    and trefid = rec.trefid 
    and ordrefid = rec.ordrefid 
    and amount = rec.amount 
    and paydt = rec.paydt 
    -- order by whatever makes sense to make the "keeper" float to top 
    order by loaddt desc 
    ; 

    for i in 2 .. duptab.count 
    loop 
     l_dupcnt := l_dupcnt + 1; 
     delete from table1 where rowid = duptab(i).row_id; 
    end loop; 

    if (mod(l_ctr, 10000) = 0) then 
     -- log to log table here (calling autonomous procedure you'll need to implement) 
     insert_logtable('Table1 deletes', 'Commit reached, deleted ' || l_dupcnt || ' rows'); 
     commit; 
    end if; 

    end loop; 
    commit; 
end; 

檢查記錄表的進展情況。

+0

爲什麼不''爲我在duptab.first.doubletab.last'(否則我同意,如果它需要15個小時)? – Ben

+0

@本嗨,本,我將第二個移到最後,保持第一個。 – tbone

0

1.並行

alter session enable parallel dml; 

DELETE /*+ PARALLEL */ FROM TABLE1 A WHERE loaddt < (
... 

假設你有企業版,一個合理的服務器配置,並且你在11g上。如果你不在11g上,那麼並行語法稍有不同。

2.降低內存需求

該計劃顯示散列連接,這可能是一件好事。但是如果沒有任何有用的過濾器,Oracle必須對整個表進行散列。 (Tbone的查詢,只使用GROUP BY,看起來更好,可能運行得更快,但它也可能遇到同樣的問題,試圖對整個表進行排序或哈希)。

如果哈希不能適應內存它必須寫入磁盤,速度可能非常慢。由於您每週運行此查詢,因此只有一個表需要查看所有行。根據運行的具體時間,您可以在查詢結尾添加類似如下內容:) where b.loaddt >= sysdate - 14。這可能會顯着減少寫入臨時表空間的數量。如果您使用像jakub.petr建議的分區策略,它也可能會減少讀IO。

3.活動報告

如果你想確切地知道你的查詢正在進行,運行Active報告:

select dbms_sqltune.report_sql_monitor(sql_id => 'YOUR_SQL_ID_HERE', type => 'active') 
from dual; 

(保存輸出到一個.html文件並打開它用瀏覽器。)