2009-11-10 22 views
3

我有一個問題,爲什麼有些SQL(在SQL Server 2005上運行)的行爲是這樣的。具體來說,我在更新期間進行了一次更改以減少鎖爭用,並且在我認爲不會的情況下似乎正在工作。爲什麼SQL更新頂部顯然減少鎖定,即使沒有記錄更新?

原始代碼:

我們有這樣的更新語句,這是被應用到表300多萬條記錄:

UPDATE USER WITH (ROWLOCK) 
SET Foo = 'N', Bar = getDate() 
WHERE ISNULL(email, '') = '' 
AND Foo = 'Y' 

正如你可能已經猜到,這似乎鎖定USER表一段時間。即使使用ROWLOCK提示,針對USER運行查詢和更新的其他作業也會阻塞,直到完成此操作。這對於這個特定的應用程序來說是不可接受的,所以我認爲我會通過讓update語句一次只更新100條記錄來應用我讀到的一個技巧。這會讓其他查詢有機會偶爾進入桌面。

改進代碼:

DECLARE @LOOPAGAIN AS BIT; 
SET @LOOPAGAIN = 1; 

WHILE @LOOPAGAIN = 1 
    BEGIN 
    UPDATE TOP (100) USER WITH (ROWLOCK) 
    SET Foo = 'N', Bar = getDate() 
    WHERE ISNULL(email, '') = '' 
    AND Foo = 'Y' 

    IF @@ROWCOUNT > 0 
     SET @LOOPAGAIN = 1 
    ELSE 
     SET @LOOPAGAIN = 0 
    END 

這並獲得成功。我們的更新完成了其工作,其他查詢能夠獲得表格。一切都是幸福與光明。

謎:

我明白這是如何提高性能時有它不得不更新表中的許多記錄。每完成100次更新後,通過循環快速運行,就可以讓其他查詢有機會進入桌面。神祕的是,即使沒有受到更新影響的記錄,此循環也具有相同的效果!

第二次運行我們的原始查詢時,它只會運行一小部分時間(比如說30秒左右),但是在那段時間內它會鎖定表,即使沒有記錄被更改。但是,將查詢放在循環中的「TOP(100)」子句中,儘管無需執行任務就花了很長時間,但是它爲其他查詢釋放了空間!

我對此很驚訝。誰能告訴我:

  1. 如果我剛纔說的是,在所有的清晰,
  2. 爲什麼第二個代碼塊允許其他的查詢在表中獲取,即使沒有被更新的記錄?
+0

我認爲在它意識到沒有要更新的行之前,仍然需要對錶進行「SELECT」操作。 – 2009-11-10 20:32:43

+1

在什麼事務隔離級別下? – 2009-11-10 21:18:25

回答

3

這聽起來像是鎖升級的經典案例。

在第一種情況下,您正在更新從您的3,000,000行表中看起來可能是大量記錄的情況。有兩件重要的事情需要考慮:

  1. 當在單個表或索引上獲取5,000個鎖時,SQL Server 2005將升級您的鎖。有關於此的警告和例外,請參閱Lock Escalation (Database Engine)瞭解更多信息。
  2. 鎖定提示,如ROWLOCK不要 防止鎖定升級。
  3. 「數據庫引擎不會 將行或鍵範圍鎖升級爲 頁鎖,但會將它們直接升級到表鎖。」

因此,基於上述和您的描述,我猜想您的查詢試圖鎖定行,正在升級到表級鎖,並阻止對用戶表的所有訪問。您會注意到這種阻塞,因爲表格很大時更新需要很長時間。

建議,避免鎖升級是:

  1. 打碎大的操作小規模的行動(你這樣做!)。
  2. 將您的查詢調整爲儘可能高效 。
  3. 作爲最後的手段,您可以設置跟蹤 標誌1211以禁用鎖升級 (不推薦!)。

有關更多詳細信息,請參見How to resolve blocking problems that are caused by lock escalation in SQL Server

如果您想驗證鎖升級是怎麼回事,您可以使用SQL Server Profiler並查看Lock:Escalation事件。

+0

鎖升級很有意義。但是如果是這樣的話,我會預期鎖定升級會在原始聲明和循環聲明中發生。 它是否足夠聰明,在循環語句中永遠不會升級到表級鎖,因爲它知道它最多可以更改100行? – 2010-01-28 20:40:10

0
  1. 很明顯。
  2. 這些條件非常重要ISNULL(email, '') = '' AND Foo = 'Y'

更新查找可能需要更新的所有行,這就是爲什麼即使沒有要更新的行也需要相同的時間。

這是一個盲注,但您應該考慮在EmailFoo兩個字段中添加一個索引(每個索引不是一個索引,而是兩個索引)。

這是唯一的沉重查詢你在這張桌子上做的?該表中的哪些索引?

+0

你可以通過在WHERE子句中刪除'ISNULL'調用來進一步改進索引的使用。優化器會喜歡'WHERE(email IS NULL OR email ='')AND foo ='Y''。 – LukeH 2009-11-11 00:55:32

+0

@Luke:這是真的嗎? isnull(email,'')=''不被它自己解釋爲(email是null或email ='')? – Ice 2009-11-16 21:53:43

0

似乎SQL Server正在根據TOP 200選擇不同的鎖,即使您指定了ROWLOCK。你能看到Management -> Activity MontiorLocks by Object下有什麼區別嗎?

0

你也應該考慮到重構的更新,如果從大的表,在我的經驗,他們有更好的表現:

更新用戶 美孚= 'N',酒吧= GETDATE()FROM (SELECT USER.ID FROM USER //可選NOLOCK提示,如果你不關心讀取未提交。 WHERE COALESCE(EMAIL, '')= '' 和Foo = 'Y')d WHERE D.ID = USER.ID