2016-03-09 57 views
8

我有一個UPDATE聲明,它可以更新超過百萬條記錄。我想分批更新它們1000或10000.我嘗試使用@@ROWCOUNT,但我無法獲得理想的結果。如何在SQL Server中更新數百萬行的大型表?

僅用於測試目的我所做的是,我選擇了包含14條記錄的表並將行數設置爲5.此查詢應該更新5,5和4中的記錄,但它只更新前5條記錄。

查詢 - 1:

SET ROWCOUNT 5 

UPDATE TableName 
SET Value = 'abc1' 
WHERE Parameter1 = 'abc' AND Parameter2 = 123 

WHILE @@ROWCOUNT > 0 
BEGIN 
    SET rowcount 5 

    UPDATE TableName 
    SET Value = 'abc1' 
    WHERE Parameter1 = 'abc' AND Parameter2 = 123 

    PRINT (@@ROWCOUNT) 
END 

SET rowcount 0 

查詢 - 2:

SET ROWCOUNT 5 

WHILE (@@ROWCOUNT > 0) 
BEGIN 
    BEGIN TRANSACTION 

    UPDATE TableName 
    SET Value = 'abc1' 
    WHERE Parameter1 = 'abc' AND Parameter2 = 123 

    PRINT (@@ROWCOUNT) 

    IF @@ROWCOUNT = 0 
    BEGIN 
     COMMIT TRANSACTION 

     BREAK 
    END 

    COMMIT TRANSACTION 
END 

SET ROWCOUNT 0 

缺少什麼我在這裏?

+0

query2有什麼問題? –

+4

請勿使用像這樣的ROWCOUNT。它已被棄用。 https://msdn.microsoft.com/en-us/library/ms188774.aspx –

+0

@JuanCarlosOropeza命令成功完成,但實際上沒有任何記錄正在更新。 – CSharper

回答

1

你的print搞砸了,因爲它重置@@ROWCOUNT。無論何時您使用@@ROWCOUNT,我的建議是總是立即將其設置爲一個變量。所以:

DECLARE @RC int; 
WHILE @RC > 0 or @RC IS NULL 
    BEGIN 
     SET rowcount 5; 

     UPDATE TableName 
      SET Value = 'abc1' 
      WHERE Parameter1 = 'abc' AND Parameter2 = 123 AND Value <> 'abc1'; 

     SET @RC = @@ROWCOUNT; 
     PRINT(@@ROWCOUNT) 
    END; 

SET rowcount = 0; 

而且,另一大優點是你不需要重複update代碼。

+0

請參閱我上面發佈的關於使用ROWCOUNT控制行更新的鏈接。 –

+0

@戈登我使用這種邏輯和查詢運行2分鐘(只有14記錄!!!)。它走向無限循環。 – CSharper

+0

@CSharper。 。 。嗯,如果沒有行被更新,那麼'@@ ROWCOUNT'應該是0,而不是NULL。無限循環的原因並不明顯。什麼是「印刷」製作?如果'update'產生'NULL',可以通過將'@ RC'設置爲任意值,然後從'WHILE'中刪除'@RC IS NULL'條件來解決。 –

11
WHILE EXISTS (SELECT * FROM TableName WHERE Value <> 'abc1') 
BEGIN 
UPDATE TOP (1000) TableName 
SET Value = 'abc1' 
WHERE Parameter1 = 'abc' AND Parameter2 = 123 
END 
+0

我已經將它從1000更新到4000,並且似乎目前工作正常。在一張表中,我正在更新500萬條記錄(似乎每10分鐘更新大約744,000條記錄)。我在開發服務器上運行它,並且將嘗試下一次更新2600萬條記錄。目前正在研究是否有辦法通過「多線程」來加速進程。 – PHBeagle

0

這是另一個有效的選項,它不使用棄用的ROWCOUNT進行更新。

while @@ROWCOUNT > 0 
begin 
    UPDATE top (1000) TableName 
    SET Value = 'abc1' 
    WHERE Parameter1 = 'abc' 
    AND Parameter2 = 123 
end 
+1

是不是一次又一次更新相同的記錄? – FLICKER

+0

@FLICKER我在想同樣的事情。 –

+0

@TT。當參數1 <>'abc'和參數2 <>'123' – Kramb

-1

首先,感謝大家的投入。我調整了我的Query - 1並得到了我想要的結果。 Gordon Linoff是正確的,PRINT被搞亂了我的查詢,所以我修改了它如下:

修改後的查詢 - 1:

SET ROWCOUNT 5 
WHILE (1 = 1) 
    BEGIN 
    BEGIN TRANSACTION 

     UPDATE TableName 
     SET Value = 'abc1' 
     WHERE Parameter1 = 'abc' AND Parameter2 = 123 

     IF @@ROWCOUNT = 0 
      BEGIN 
       COMMIT TRANSACTION 
       BREAK 
      END 
    COMMIT TRANSACTION 
    END 
SET ROWCOUNT 0 

輸出:

(5 row(s) affected) 

(5 row(s) affected) 

(4 row(s) affected) 

(0 row(s) affected) 
9
  1. 你不應該更新除非您確定該操作正在獲取頁面鎖定(由於每個頁面有多行是UPDATE操作的一部分),否則將不會有10k行。問題是鎖升級(從行或頁面到表鎖)發生在5000 。所以最好保持在5000以下,以防萬一操作使用Row Locks。

  2. 您應該不是使用SET ROWCOUNT來限制將被修改的行數。這裏有兩個問題:

    1. 它是自從SQL Server 2005的發佈棄用(11年前):

      使用SET ROWCOUNT將不會影響DELETE,INSERT和UPDATE語句未來版本的SQL Server。避免在新的開發工作中使用SET ROWCOUNT和DELETE,INSERT和UPDATE語句,並計劃修改當前使用它的應用程序。對於類似的行爲,使用TOP語法

    2. 它可以影響不僅僅是語句的更多,你正在處理:

      設置SET ROWCOUNT選項導致大多數的Transact-SQL語句停止處理時它們受到指定行數的影響。這包括觸發器。 ROWCOUNT選項不影響動態遊標,但它確實限制了鍵集和不敏感遊標的行集。這個選項應該謹慎使用。

    相反,使用TOP()子句。

  3. 這裏沒有明確的交易目的。它使代碼複雜化,並且你沒有處理ROLLBACK,因爲每個語句都是它自己的事務(即自動提交),所以甚至不需要ROLLBACK。

  4. 假設您找到保持顯式事務的原因,那麼您沒有TRY/CATCH結構。請參閱我的回答DBA.StackExchange爲處理事務一個try/catch模板:

    Are we required to handle Transaction in C# Code as well as in Store procedure

我懷疑WHERE子句未在問題示例代碼顯示的真實,如此單純依靠什麼已經表明,一個更好的模式是:

DECLARE @Rows INT, 
     @BatchSize INT; -- keep below 5000 to be safe 

SET @BatchSize = 2000; 

SET @Rows = @BatchSize; -- initialize just to enter the loop 

BEGIN TRY  
    WHILE (@Rows = @BatchSize) 
    BEGIN 
     UPDATE TOP (@BatchSize) tab 
     SET tab.Value = 'abc1' 
     FROM TableName tab 
     WHERE tab.Parameter1 = 'abc' 
     AND tab.Parameter2 = 123 
     AND tab.Value <> 'abc1' COLLATE Latin1_General_100_BIN2; 
     -- Use a binary Collation (ending in _BIN2, not _BIN) to make sure 
     -- that you don't skip differences that compare the same due to 
     -- insensitivity of case, accent, etc, or linguistic equivalence. 

     SET @Rows = @@ROWCOUNT; 
    END; 
END TRY 
BEGIN CATCH 
    RAISERROR(stuff); 
    RETURN; 
END CATCH; 

通過測試@Rows@BatchSize,那麼就可以避免最後的更新查詢(在大多數情況下),因爲最後一組通常是有些麻木了呃行數小於@BatchSize,在這種情況下,我們知道沒有其他處理(這是您在answer中顯示的輸出中看到的內容)。只有在最後一組行等於@BatchSize的情況下,此代碼纔會運行影響0行的最終UPDATE。

我還爲WHERE子句添加了一個條件,以防止已更新的行再次更新。

+0

如果我不更新一列,但假設有10列?我必須比較所有列的值嗎?什麼是最高性能的方法? – asemprini87

+0

@ asemprini87儘可能進行比較以減少不必要的更新,因爲它們需要更長的時間並增加日誌文件。我只是更新了我的答案,包括強制「Value」過濾器的二進制排序規則,但您可以在任何其他字符串列上使用「COLLATE Latin1_General_100_BIN2」來加速字符串匹配,假設您只查找完全匹配, t需要考慮套管差異等。我想提及爲該操作創建一個過濾索引,但是如果必須爲每個批次更新,它可能不會更快。雖然可能值得測試。 –

3

我想分享我的經驗。前幾天,我必須更新2100萬條記錄,其中有7600萬條記錄。我的同事提出了下一個變體。 例如,我們有一個表「人」:

Id | FirstName | LastName | Email   | JobTitle 
1 | John  | Doe  | [email protected]  | Software Developer 
2 | John1  | Doe1 | [email protected]  | Software Developer 
3 | John2  | Doe2 | [email protected]  | Web Designer 

任務:更新的人到新的職位:「軟件開發」 - >「Web開發」。

1.創建臨時表 'Persons_SoftwareDeveloper_To_WebDeveloper(ID INT主鍵)'

2.選擇到要與新職位更新臨時表的人:

INSERT INTO Persons_SoftwareDeveloper_To_WebDeveloper SELECT Id FROM 
Persons WITH(NOLOCK) --avoid lock 
WHERE JobTitle = 'Software Developer' 
OPTION(MAXDOP 1) -- use only one core 

取決於行數,這個語句將需要一些時間來填充臨時表,但它會避免鎖。在我的情況下,大約需要5分鐘(2100萬行)。

3.主要思路是生成微型sql語句來更新數據庫。所以,讓我們把它們打印:

DECLARE @i INT, @pagesize INT, @totalPersons INT 
    SET @i=0 
    SET @pagesize=2000 
    SELECT @totalPersons = MAX(Id) FROM Persons 

    while @i<= @totalPersons 
    begin 
    Print ' 
    UPDATE persons 
     SET persons.JobTitle = ''ASP.NET Developer'' 
     FROM Persons_SoftwareDeveloper_To_WebDeveloper tmp 
     JOIN Persons persons ON tmp.Id = persons.Id 
     where persons.Id between '+cast(@i as varchar(20)) +' and '+cast(@[email protected] as varchar(20)) +' 
     PRINT ''Page ' + cast((@i/@pageSize) as varchar(20)) + ' of ' + cast(@totalPersons/@pageSize as varchar(20))+' 
    GO 
    ' 
    set @[email protected][email protected] 
    end 

執行這個腳本後,您會收到成百上千,你可以在MS SQL Management Studio中的一個選項卡執行批次。

4.運行打印的sql語句並檢查表上的鎖。您總是可以停止流程並使用@pageSize來加速或減速更新(在暫停腳本之後,請勿忘記更改@i)。

5. Drop Persons_SoftwareDeveloper_To_AspNetDeveloper。刪除臨時表。

次要注意事項:此遷移可能需要一段時間,遷移過程中可能插入帶有無效數據的新行。所以,首先修復你的行添加的地方。在我的情況下,我修復了UI,'Software Developer' - >'Web Developer'。

相關問題