2009-05-22 104 views
11

我在SQL Server 2005中有一個表,其中大約有40億行。我需要刪除大約20億這些行。如果我嘗試在單個事務中執行此操作,事務日誌將填滿並失敗。我沒有任何額外的空間來增加事務日誌。我認爲最好的方法是批量刪除語句(批量10,000〜)。SQL批處理刪除

我可以使用遊標來做到這一點,但是這是一個標準/簡單/巧妙的方式嗎?

P.S.該表沒有身份列作爲PK。 PK由一個整數外鍵和一個日期組成。

+0

嗯,聽起來像歷史/週期性數據... – 2009-05-22 14:55:22

回答

7

你可以「蠶食」刪除,這也意味着你不會對數據庫造成巨大的負載。如果您的t-log備份每10分鐘運行一次,那麼您應該可以在相同的時間間隔內運行一次或兩次。你可以安排它作爲一個SQL代理作業

嘗試這樣:

DECLARE @count int 
SET @count = 10000 

    DELETE FROM table1 
    WHERE table1id IN (
     SELECT TOP (@count) tableid 
     FROM table1 
     WHERE x='y' 
    ) 
2

那麼,如果您使用SQL Server分區,根據日期列說,你可能會切換出不再需要的分區。也許考慮未來的實施。

我認爲最好的選擇可能就像你說的那樣,以較小的批次刪除數據,而不是一次打開,以避免任何潛在的阻塞問題。

你也可以考慮以下方法:

  1. 的數據複製,以保持到一個臨時表
  2. 截斷原始表,以清除所有數據
  3. 移動一切從臨時表回原始表格

由於數據已添加回原始表格,因此您的索引也會重建。

+0

感謝您的回答,我們已經研究到分區,但它不是實際的爲我們在實現它瞬間(部分由於這個問題:http:// support。microsoft.com/kb/924601)。 關於將數據複製到臨時表:操作是否需要比刪除行少的事務日誌空間? – 2009-05-22 08:14:59

+0

可能是的,因爲你不需要發出DELETE操作。一旦創建了該表的副本,就會截斷源表,然後僅複製希望保留回源表的數據。儘管如此,我仍然建議您使用批量刪除,因爲您確實希望記錄所有操作,以確保數據庫的一致性/可恢復性。 – 2009-05-22 08:31:15

3

這樣的聲音是一次性的操作(我希望對你有好處),而且你不需要返回到這個批處理刪除中途的狀態 - 如果這就是你爲什麼不切換到SIMPLE事務模式在運行之前,然後在完成後返回FULL?

這樣,事務日誌將不會增長太多。這在大多數情況下可能並不理想,但我在這裏沒有看到任何錯誤(假設如上所述,您不需要返回到刪除之間的狀態)。

你可以用SMT做到這一點在你的腳本,如:

ALTER DATABASE myDB SET RECOVERY FULL/SIMPLE 

或者,你可以設置一個工作收縮事務日誌的時間每個給定的間隔 - 而你刪除正在運行。這有點不好,但我認爲它會做到這一點。

+0

是的,這是一個一次性的操作:) 不幸的是,我們已經在使用簡單的恢復,但即使是簡單的恢復,當在單個事務中進行刪除時,tlog(100GB)也會填滿。 – 2009-05-22 08:29:46

+1

這裏值得一提的是,它將通過切換到簡單恢復來使任何事務備份無效。如果沒有被使用,那就沒問題了(我實際上使用這種方式很多),但是之後需要完整或差異備份才能夠再次使用事務備份。 – 2009-05-22 08:31:03

+0

「替代」解決方案/破解? :) – JohnIdol 2009-05-22 08:55:59

8

什麼區別你想要刪除的行和你想保留的行?請問這是否適合您:

while exists (select 1 from your_table where <your_condition>) 
delete top(10000) from your_table 
where <your_condition> 
-1

簡而言之,您不能刪除20億行而不會導致某種主要數據庫停機。

最好的選擇可能是將數據複製到臨時表並截斷原始表,但這會填充您的tempDB,並且使用的記錄數不會少於刪除數據。

您需要刪除儘可能多的行,直到事務日誌填滿,然後每次都截斷它。Stanislav Kniazev提供的答案可以通過增加批量大小和添加調用來截斷日誌文件來修改。

2

我會做類似臨時表的建議,但我會選擇一個新的永久表,你想保留的行,刪除原來的表,然後重新命名新的。這應該具有相對較低的轉錄影響。顯然記得重新命名後重新創建新表所需的任何索引。

只是我的兩個p'enneth。

2

除了把這個批處理與語句以截斷日誌,你可能也想嘗試這些技巧:

  • 添加第一列除了匹配您的聚集索引標準的其他標準
  • 從表中刪除所有索引,然後把它們放回刪除完成後,如果可能的話,也不會在terfere與任何其他事情的數據庫,但保留聚集索引

對於第一點以上,例如,如果你的PK是羣集然後找這大約要行數相匹配範圍刪除每批和使用:

DECLARE @max_id INT, @start_id INT, @end_id INT, @interval INT 
SELECT @start_id = MIN(id), @max_id = MAX(id) FROM My_Table 
SET @interval = 100000 -- You need to determine the right number here 
SET @end_id = @start_id + @interval 

WHILE (@start_id <= @max_id) 
BEGIN 
    DELETE FROM My_Table WHERE id BETWEEN @start_id AND @end_id AND <your criteria> 

    SET @start_id = @end_id + 1 
    SET @end_id = @end_id + @interval 
END 
0

我與誰想要你在一個較小的記錄集循環的人都同意,這將是比試圖做一個步驟整個操作速度更快。您可以體驗循環中應包含的記錄數量。每次大約2000個似乎是大多數表格中的甜蜜點,我從大部分表格中刪除了一些數據,例如500個。取決於外鍵的數量,記錄的大小,觸發器等等,所以它確實需要一些嘗試找到你需要的東西。這也取決於表格的使用有多沉重。高度訪問的表格將需要循環的每次迭代運行更短的時間。如果您可以在非工作時間運行,或者以單用戶模式運行,那麼您可以在一個循環中刪除更多記錄。

如果您不認爲您在下班時間的某個夜晚會這樣做,最好是使用計數器設計循環,並且每晚只做一組迭代,直到完成爲止。此外,如果使用隱式事務而不是顯式事務,則可以隨時終止循環查詢,並且已刪除的記錄將保持刪除狀態,除了當前循環中的那些記錄以外。比試圖回滾50萬條記錄要快得多,因爲你已經讓系統停下來了。

在進行這種操作之前立即備份數據庫通常是一個好主意。

0

這是我的例子:

-- configure script 
-- Script limits - transaction per commit (default 10,000) 
-- And time to allow script to run (in seconds, default 2 hours) 
-- 
DECLARE @MAX INT 
DECLARE @MAXT INT 
-- 
-- These 4 variables are substituted by shell script. 
-- 
SET @MAX = $MAX 
SET @MAXT = $MAXT 
SET @TABLE = $TABLE 
SET @WHERE = $WHERE 

-- step 1 - Main loop 
DECLARE @continue INT 
-- deleted in one transaction 
DECLARE @deleted INT 
-- deleted total in script 
DECLARE @total INT 
SET @total = 0 
DECLARE @max_id INT, @start_id INT, @end_id INT, @interval INT 
SET @interval = @MAX 
SELECT @start_id = MIN(id), @max_id = MAX(id) from @TABLE 
SET @end_id = @start_id + @interval 

-- timing 
DECLARE @start DATETIME 
DECLARE @now DATETIME 
DECLARE @timee INT 
SET @start = GETDATE() 
-- 
SET @continue = 1 
IF OBJECT_ID (N'EntryID', 'U') IS NULL 
BEGIN 
    CREATE TABLE EntryID (startid INT) 
    INSERT INTO EntryID(startid) VALUES(@start_id) 
END 
    ELSE 
BEGIN 
    SELECT @start_id = startid FROM EntryID 
END 


WHILE (@continue = 1 AND @start_id <= @max_id) 
BEGIN 

    PRINT 'Start issued: ' + CONVERT(varchar(19), GETDATE(), 120) 
    BEGIN TRANSACTION 
     DELETE 
     FROM @TABLE 
     WHERE id BETWEEN @start_id AND @end_id AND @WHERE 
     SET @deleted = @@ROWCOUNT 
    UPDATE EntryID SET EntryID.startid = @end_id + 1 
    COMMIT 
    PRINT 'Deleted issued: ' + STR(@deleted) + ' records. ' + CONVERT(varchar(19), GETDATE(), 120) 
    SET @total = @total + @deleted 
    SET @start_id = @end_id + 1 
    SET @end_id = @end_id + @interval 
    IF @end_id > @max_id 
     SET @end_id = @max_id 

    SET @now = GETDATE() 
    SET @timee = DATEDIFF (second, @start, @now) 
    if @timee > @MAXT 
    BEGIN 
    PRINT 'Time limit exceeded for the script, exiting' 
    SET @continue = 0 
    END 
-- ELSE 
-- BEGIN 
--  SELECT @total 'Removed now', @timee 'Total time, seconds' 
-- END 
END 

SELECT @total 'Removed records', @timee 'Total time sec' , @start_id 'Next id', @max_id 'Max id', @continue 'COMPLETED? ' 
SELECT * from EntryID next_start_id 

GO