2016-03-14 66 views
1

我使用以下sql查詢來更新MyTable。代碼需要5到15分鐘。只要ROWS < = 100000000更新MyTabel,但當行數> 100000000時,需要指數時間更新MYTable。我如何更改此代碼以使用set-base而不是while循環?如何通過避免循環來優化這個t-sql腳本代碼?

DECLARE @startTime DATETIME 
DECLARE @batchSize INT 
DECLARE @iterationCount INT 
DECLARE @i INT 
DECLARE @from INT 
DECLARE @to INT 

SET @batchSize = 10000 
SET @i = 0 

SELECT @iterationCount = COUNT(*)/@batchSize 
FROM MyTable 
WHERE LitraID = 8175 
    AND id BETWEEN 100000000 AND 300000000 

WHILE @i <= @iterationCount BEGIN 

    BEGIN TRANSACTION T 

    SET @startTime = GETDATE() 
    SET @from = @i * @batchSize 
    SET @to = (@i + 1) * @batchSize - 1 

    ;WITH data 
    AS (
     SELECT DoorsReleased, ROW_NUMBER() OVER (ORDER BY id) AS Row 
     FROM MyTable 
     WHERE LitraID = 8175 
      AND id BETWEEN 100000000 AND 300000000 
    ) 
    UPDATE data 
    SET DoorsReleased = ~DoorsReleased 
    WHERE row BETWEEN @from AND @to 

    SET @i = @i + 1 

    COMMIT TRANSACTION T 

END 
+2

編輯你的問題,並解釋你正在嘗試做什麼。 –

+0

請爲您的數據庫和執行計劃提供一個恢復模型,用於單次迭代 – Devart

+1

使用一些實際和索引列來計數批次而不是ROW_NUMBER處理。你爲什麼不使用'ID'本身? –

回答

1

這將消除環路

UPDATE MyTable 
    set DoorsReleased = ~DoorsReleased 
WHERE LitraID = 8175 
    AND id BETWEEN 100000000 AND 300000000 
    AND DoorsReleased is not null -- if DoorsReleased is nullable 
-- AND DoorsReleased <> ~DoorsReleased</strike> 

如果你是在循環設定
下面將工作
我想〜是列名的一部分,但它是一個沒有運營商

select 1; 
WHILE (@@ROWCOUNT > 0) 
BEGIN 
    UPDATE top (100000) MyTable 
     set DoorsReleased = ~DoorsReleased 
    WHERE LitraID = 8175 
     AND id BETWEEN 100000000 AND 300000000 
     AND (  DoorsReleased <> ~DoorsReleased 
      or ( DoorsReleased is null and ~DoorsReleased is not null) 
      ) 
END 

在交易中我不認爲環路ng會有價值,因爲事務日誌無法清除。和10000批次尺寸小。\

在評論說,如果你想循環再利用id作爲ROW_NUMBER()所有這些循環嘗試是昂貴

您可能能夠使用OFFSET

+1

如果DoorsReleased是一個不可空的位,那麼'DoorsReleased <>〜DoorsReleased'總是成立的。這意味着循環將是一個無限循環,因爲它將繼續處理相同的100,000條記錄,將值從0切換到1,然後再次返回。 –

+0

@AndyNichols DoorsReleased <>〜DoorsReleased不是真的,如果DoorsReleased =〜DoorsReleased – Paparazzi

+0

@AndyNichols我不知道〜是一個非運營商的位 – Paparazzi

1

您的問題之一是,您循環中的select語句獲取LitraID = 8175的所有記錄,設置行號,然後在update語句中過濾。這發生在每次迭代中。

一種方法是在進入循環並將其存儲在臨時表中之前獲取所有更新的ID。然後你可以寫一個類似的查詢到你所擁有的查詢,但加入這個ID表。

但是,如果您知道LitraID = 8175有多少條記錄,並且它們遍佈整個表格,而不是與類似的ID捆綁在一起,那麼還有更簡單的方法。

DECLARE @batchSize INT 
DECLARE @minId INT 
DECLARE @maxId INT 

SET @batchSize = 10000 --adjust according to how frequently LitraID = 8175, larger numbers if infrequent 
SET @minId = 100000000 

WHILE @minId <= 300000000 BEGIN 

    SET @maxId = @minId + @batchSize - 1 
    IF @maxId > 300000000 BEGIN 
     SET @maxId = 300000000 
    END 

    BEGIN TRANSACTION T 

     UPDATE MyTable 
     SET DoorsReleased = ~DoorsReleased 
     WHERE id BETWEEN @minId AND @maxId 

    COMMIT TRANSACTION T 

    SET @minId = @maxId + 1 
END 

這將使用id的值來控制循環,這意味着你不需要額外的步驟來計算@iterationCount。它使用小批量,以便表格不會長時間鎖定。它沒有任何不必要的SELECT語句,假設id有索引,更新中的WHERE子句是有效的。

它不會有完全相同數量的記錄在每次交易更新,但沒有理由需要。

+0

仍然需要很長時間。我在3個小時後停止了查詢,但沒有完成。 – Nokomo

+0

陳述是一種交易,因此交易不做任何事情。還是很好的答案。 – Paparazzi

+0

@Nokomo然後你需要看另一個整體設計。沒有TSQL修復程序。在有效桌子上翻轉數百萬行很奇怪。 – Paparazzi