2009-12-03 125 views
1

編輯:請回答我問的兩個答案之一。我知道還有其他的選擇,在另一種情況下會更好。這些其他潛在的選項(分區表,作爲一個大批量刪除語句不分批提交等)是不是選項在我的情況下,由於我的控制之外的事情。SQL優化問題(oracle)

我有幾個非常大的表格要刪除。所有的索引都有相同的外鍵。我需要刪除所有表中的某些記錄。

table source 
    id --primary_key 
    import_source --used for choosing the ids to delete 

table t1 
    id --foreign key 
    --other fields 

table t2 
    id --foreign key 
    --different other fields 

通常做了刪除這樣的時候,我把一個循環來通過所有的ID:

declare 
my_counter integer := 0; 
begin 
for cur in (
select id from source where import_source = 'bad.txt' 
) loop 
    begin 
    delete from source where id = cur.id; 
    delete from t1 where id = cur.id; 
    delete from t2 where id = cur.id; 
    my_counter := my_counter + 1; 
    if my_counter > 500 then 
     my_counter := 0; 
     commit; 
    end if; 
    end; 
    end loop; 
    commit; 
end; 

然而,在一些代碼,我看到了其他地方,它被放在一起單獨的循環,每個刪除一個。

declare 
type import_ids is table of integer index by pls_integer; 
my_count integer := 0; 
begin 
select id bulk collect into my_import_ids from source where import_source = 'bad.txt' 

for h in 1..my_import_ids.count 
    delete from t1 where id = my_import_ids(h); 
    --do commit check 
end loop; 
for h in 1..my_import_ids.count 
    delete from t2 where id = my_import_ids(h); 
    --do commit check 
end loop; 

--do commit check will be replaced with the same chunk to commit every 500 rows as the above query 

所以我需要以下回答之一:

1)哪一個是更好?

2)如何找出哪個更適合我的特殊情況? (也就是說,如果這取決於我有多少表,他們是多麼大等)

編輯:

必須這樣做在一個循環中,由於這些表的大小。我將從具有數億條記錄的表中刪除數千條記錄。這發生在一個無法承受這麼長時間表鎖定的系統上。

編輯:

注:我需要分批提交。數據量太大,無法在一批中完成。回滾表將使我們的數據庫崩潰。

如果有一種方法可以批量提交而不是循環,我願意聽到它。否則,不要打擾說我不應該使用循環...

+1

如果您覺得由於表的大尺寸而必須使用循環,那麼您(和/或DBA)是否使用數據庫引擎的分區功能來查看?這是分區幫助的「用例」之一。例如,如果您擁有10 TB的10 TB行表,那麼刪除分區(基於ID)比遍歷數百萬行更容易。 – JasDev 2009-12-03 17:43:01

+1

是的,我們已經考慮過這個。我們已經分割了我們的數據庫。但是,t1和t2(etc)表可以通過幾種不同的方式訪問(基於id以外的字段)。因此,任何對它們的分割都會傷害整體。 我遺漏了很多不影響我的問題的細節,但確實意味着我們無法對t1,t2等進行分區。 – 2009-12-03 18:00:00

+1

您知道無論刪除多少行,您都不會鎖好桌子吧?如果您正在刪除某個其他進程嘗試更新的行,則只會出現鎖爭用,這似乎不太可能。如果有人試圖更新您嘗試刪除的行,那麼阻止該行顯得非常合理。 – 2009-12-04 06:01:23

回答

6

大衛, 如果硬要commiting,您可以使用下面的代碼:

declare 
    type import_ids is table of integer index by pls_integer; 
    my_import_ids import_ids; 
    cursor c is select id from source where import_source = 'bad.txt'; 
begin 
    open c; 
    loop 
    fetch c bulk collect into my_import_ids limit 500; 
    forall h in 1..my_import_ids.count 
     delete from t1 where id = my_import_ids(h); 
    forall h in 1..my_import_ids.count 
     delete from t2 where id = my_import_ids(h); 
    commit; 
    exit when c%notfound; 
    end loop; 
    close c; 
end; 

這項計劃由500行件,刪除和commiting每一塊取的ID。它應該比逐行處理快得多,因爲bulk collectforall可以作爲單個操作(在往返數據庫的單往返中),從而最大限度地減少了上下文切換的次數。有關詳細信息,請參見Bulk Binds, Forall, Bulk Collect

+0

隨着光標內的提交,我想知道是否最好將ORDER BY插入到光標select中以確保在提交之前從源表中讀取所有值,希望也可以減少快照的機會舊錯誤。你會想要檢查執行計劃,以確保當然正在執行排序。 – 2009-12-04 07:52:07

+0

David Aldridge:嗯,我們是否需要刪除「源」表中的行?我忽略了它。但ORDER BY什麼?我們想要對行進行排序,以便我們逐塊讀取它們,而不是再次返回到上一個塊,對吧?我認爲,全面掃描訪問將做到這一點。 – 2009-12-04 09:00:57

7

爲什麼循環?

delete from t1 where id IN (select id from source where import_source = 'bad.txt'; 
delete from t2 where id IN (select id from source where import_source = 'bad.txt'; 
delete from source where import_source = 'bad.txt' 

這是使用標準的SQL。我並不特別瞭解Oracle,但許多DBMS還具有基於多表的基於JOIN的DELETE,可以讓您在單個語句中完成整個事情。

+0

+1和一個可樂打我幾秒鐘。 – 2009-12-03 17:25:05

+0

不可能。由於這些表格有多大,我必須*在循環中做到這一點,隨時提交。 – 2009-12-03 17:28:54

+0

我不是甲骨文公司的人,所以我不能質疑你們的DBA的聲明,但是我發現很難相信甲骨文沒有提供某種設置來執行這些DELETE命令。對於主要的SQL數據庫,您無法發佈如此簡單的SQL命令是很困難的。 – 2009-12-03 17:58:39

1

首先,你不應該在循環中使用commit--它不是有效的(會產生大量的重做),如果發生錯誤,你不能回滾。

正如前面的答案中提到的那樣,您應該發出單個delete s,或者,如果您要刪除大部分記錄,那麼使用剩餘行創建新表可以更優化,刪除舊錶並刪除舊錶並重命名新表以舊名稱。

事情是這樣的:

CREATE TABLE new_table as select * from old_table where <filter only remaining rows>; 

index new_table 
grant on new table 
add constraints on new_table 
etc on new_table 

drop table old_table 
rename new_table to old_table; 

參見Ask Tom

+0

我*必須*提交一個循環。這是來自我們DBA的訂單 - 如果我不這樣做,回滾信息將會過大,並會導致我們的系統崩潰。 我正在刪除一小部分記錄,所以創建新的和刪除是不可行的。 – 2009-12-03 17:30:49

+1

在循環提交會給你ORA-01555錯誤,而不是提到性能較差。您的DBA應該增加回滾段大小。 – Majkel 2009-12-03 17:42:11

+0

不可行。我希望這個刪除操作需要幾小時才能完成。我們無法將生產數據庫上的表鎖定很長時間。 – 2009-12-03 18:03:57

1

拉里·勒斯蒂格是正確的,你不需要一個循環。儘管如此,在以較小的塊進行刪除時可能會有一些好處。這裏PL/SQL批量綁定可以大大提高速度:

declare 
type import_ids is table of integer index by pls_integer; 
my_count integer := 0; 
begin 
select id bulk collect into my_import_ids from source where import_source = 'bad.txt' 

forall h in 1..my_import_ids.count 
    delete from t1 where id = my_import_ids(h); 
forall h in 1..my_import_ids.count 
    delete from t2 where id = my_import_ids(h); 

的方式我寫它,一次就全部,在這種情況下是啊單一SQL更好。但是你可以改變你的循環條件,把它分成塊。關鍵點是

  • 不對每一行提交。如果有的話,只提交每N行。
  • 使用N的塊時,不要在普通循環中運行刪除。使用forall作爲批量綁定運行刪除,速度更快。

除了提交的開銷之外,原因是每次在PL/SQL代碼中執行SQL語句時,它本質上都會執行上下文切換。批量綁定避免這一點。

+0

你的第一點:是的,我做了500行左右的排。 – 2009-12-03 18:01:26

+0

第二點:你能提供更多細節forall的工作原理嗎?如何重新編寫查詢以使用forall,但仍然批量提交 – 2009-12-03 18:02:31

0

無論如何,您可以嘗試使用分區來使用並行執行,而不僅僅是刪除一個分區。 The Oracle documentation可能證明這是有用的。在這種情況下,每個分區都會使用它自己的回滾段。

+0

由於我的控制之外的考慮事項,對t1,t2等表進行分區不是一個選項。 – 2009-12-03 18:30:33

+1

那麼,我回答這樣的問題: 1.您的解決方案都適合您的情況。 2.您可以通過測試確定哪個更好。在模擬環境中做兩件事,看看會發生什麼。 – 2009-12-03 21:02:03

0

如果您在t1/t2刪除之前正在從源刪除,這表明您沒有參照完整性約束(否則會出現說明存在子記錄的錯誤)。

我會去用ON DELETE CASCADE創建約束。然後一個簡單的

DECLARE 
    v_cnt NUMBER := 1; 
BEGIN 
    WHILE v_cnt > 0 LOOP 
    DELETE FROM source WHERE import_source = 'bad.txt' and rownum < 5000; 
    v_cnt := SQL%ROWCOUNT; 
    COMMIT; 
    END LOOP; 
END; 

子記錄會自動刪除。

如果你不能有ON DELETE CASCADE,我會用ON全局臨時表去COMMIT DELETE ROWS

DECLARE 
    v_cnt NUMBER := 1; 
BEGIN 
    WHILE v_cnt > 0 LOOP 
    INSERT INTO temp (id) 
    SELECT id FROM source WHERE import_source = 'bad.txt' and rownum < 5000; 
    v_cnt := SQL%ROWCOUNT; 
    DELETE FROM t1 WHERE id IN (SELECT id FROM temp); 
    DELETE FROM t2 WHERE id IN (SELECT id FROM temp); 
    DELETE FROM source WHERE id IN (SELECT id FROM temp); 
    COMMIT; 
    END LOOP; 
END; 

我還去爲你的DBA將允許最大的一塊。 我希望每筆交易至少持續一分鐘。更頻繁的提交將是一種浪費。

這是發生這 不起有表的系統上鎖定 那麼久。

Oracle不鎖定表,只鎖定行。我假設沒有人會鎖定你正在刪除的行(或者至少不會很長)。所以鎖定不是問題。