1. 程式人生 > 實用技巧 >SQL Server-聚焦NOLOCK、UPDLOCK、HOLDLOCK、READPAST你弄懂多少?

SQL Server-聚焦NOLOCK、UPDLOCK、HOLDLOCK、READPAST你弄懂多少?

前言

[轉發自:https://www.cnblogs.com/CreateMyself/p/6512692.html]

時間流逝比較快,博主也在快馬加鞭學習SQL Server,下班回來再晚也不忘記更新下部落格,時間擠擠總會有的,現在的努力求的是未來所謂的安穩,每學一門為的是深度而不是廣度,求的是知識自成體系而不是零散,廢話不多說本節我們來講講SQL Server基礎系列最後幾節內容,這話博主說了n次,呵呵。

NOLOCK和READPAST

NOLOCK

隨便翻翻部落格園對於各種鎖的介紹真的是一個字【多】,僅僅介紹其概念,再要麼就是轉載其概念,不知道那些轉載概念的園友是否已經弄懂了,稍微發下感慨。NOLOCK在概念上類似於READ UNCOMMITTED隔離級別,並且只針對於SELECT查詢語句,它不會獲取表的共享鎖,換句話說不會阻止排它鎖來更新資料行。當我們對錶進行NOLOCK有什麼好處呢?它能夠提高併發效能,因為此時SQL Server資料庫引擎不必去維護共享鎖,由於不會對正在讀取的表獲取共享鎖,所以可能導致未提交的事務也會被讀取,所以此時缺點顯而易見將導致髒讀,至於髒讀是何含義則無需我再多講。我們重點的明白什麼情況下應該用NOLOCK。我們看下實際例子來理解NOLOCK,建立測試表並插入300條測試資料:

IF OBJECT_ID('Example')>0      

DROP TABLE Example;

GO

CREATE TABLE [dbo].[Example]

(      
      [SaleID] [int] IDENTITY(1,1) NOT NULL PRIMARY KEY,      
      [Product] [char](150) NULL,      
            [SaleDate] [datetime] NULL,      
            [SalePrice] [money] NULL
)

GO


DECLARE @i SMALLINT

SET @i = 1

WHILE (@i <=100)

BEGIN                  
      INSERT INTO Example              
      (Product, SaleDate, SalePrice)                  
      VALUES      
      ('Computer', DATEADD(mm, @i, '3/11/1919'), DATEPART(ms, GETDATE()) + (@i + 57))      


      INSERT INTO Example               
      (Product, SaleDate, SalePrice)      
      VALUES             
      ('BigScreen', DATEADD(mm, @i, '3/11/1927'), DATEPART(ms, GETDATE()) + (@i + 13))                  


      INSERT INTO Example                  
      (Product, SaleDate, SalePrice)          
      VALUES            
      ('PoolTable', DATEADD(mm, @i, '3/11/1908'), DATEPART(ms, GETDATE()) + (@i + 29))                  


      SET @i = @i + 1

END

GO

此時我們再來插入一條測試資料:

BEGIN TRANSACTION
      INSERT INTO Example
      (Product, SaleDate, SalePrice)          
      VALUES            
      ('PoolTable', GETDATE(), 500) 

此時我們保持該事務視窗開啟,所以此時在表中仍然會記錄著對其所發出的鎖,接下來我們在另外一個視窗查詢表中資料總行數並使用NOLOCK提示。

SELECT COUNT(*) FROM Example WITH(NOLOCK)

此時顯示資料總函式為301,因為上述插入語句的事務進入到了表中只是並未提交而已,此時我們不想插入那條資料進行撤銷即回滾

BEGIN TRANSACTION
      INSERT INTO Example
      (Product, SaleDate, SalePrice)          
      VALUES            
      ('PoolTable', GETDATE(), 500) 
ROLLBACK TRANSACTION

此時我們回滾了之前插入的資料,我們再來利用NOLOCK提示來查詢資料總函式。

此時返回的為實際總資料行,而我麼第一次查詢的資料並未提交這就是典型的-髒讀。

READPAST

READPAST表提示相信很多童鞋用的比較少,但是實際上其作用非常大,當在表中用READPAST指定提示時此時SQL Server資料庫引擎在返回結果集時將不會返回鎖定的行或者資料頁。它除了和NOLOCK一樣不會導致查詢阻塞外,因為不會返回鎖定的行記錄所以其優點好包括不存在髒讀。但是其缺點則是因為不包含鎖定的行記錄但是很難保證結果集或者修改語句是否包含我們所必須需要返回的行。有可能在我們的業務邏輯中,需要返回我們必須需要的行。它的使用方式和NOLOCK一樣,下面我們來看下實際例子,更新測試表中的SalePrice列,如下:

BEGIN TRANSACTION
      UPDATE TOP(1) Example
      SET SalePrice = SalePrice + 1

由於我們並未提交或者回滾事務所以此時更新的資料行已經被影響,下面我們利用READPAST提示來查詢表中總資料行。

SELECT COUNT(*)

FROM Example WITH(READPAST)

在我們的測試表中資料行為300條,同時我們進行了上述更新,當我們利用READPAST提示進行查詢總資料行時,因為更新而未提交或者回滾導致此時有一行記錄被排它鎖鎖住,而READPAST的作用則是跳過鎖住的行,所以此時很明顯只返回299條資料,如下:

通過上述圖顯示由於更新資料行被鎖定,所以此時利用READPAST來查詢總資料行時導致更新資料行將被忽略。

UPDLOCK和HOLDLOCK

UPDLOCK

怎麼會出現一個更新鎖的呢,原來我們對於查詢和更新死鎖說到了排它鎖,這個排它鎖和更新鎖不是一樣的麼,此言差矣,容我娓娓道來,這個UPDLOCK只是針對於表中的某一行記錄來鎖定從而阻止其他操作對該行的資料更新,說到這裡想必我們已經明瞭,UPDLOCK是行級別,而排它鎖則是表級別,二者不可同日而語。也就說當我們對某一行新增UPDLOCK提示時並不會阻塞其他查詢操作,下面我們來看看,我們開啟一個視窗來更新測試表中篩選條件為SaleID等於1的記錄並用UPDLOCK鎖住。

 BEGIN TRAN
 select * from Example WITH (UPDLOCK) where SaleID = 1

此時我們再來開一個視窗進行查詢,如下:

select * from Example

此時我們將看到能夠查詢出所有資料,如下:

HOLDLOCK

這個又是什麼玩意了,根據詞達意翻譯為厚住鎖【哈哈】,這個翻譯雖然有點勉強,但是非常明確的表達了其意思,有點強制性的意味,當我們使用HOLDLOCK提示時,此時查詢將鎖定表且被強制序列化,直到事務完成,才會被釋放,其類似於SERIALIZABLE最高隔離級別。我們結合上述例子來看下,當我們對錶進行HOLDLOCK後再進行查詢

 BEGIN TRAN
 select * from Example WITH (UPDLOCK,HOLDLOCK) where SaleID = 1

此時我們再來執行查詢

 select * from Example

什麼情況還是能查詢出資料,不知道看到本文的你是否心生疑竇,我們並未提交事務並用UPDLOCK和HOLDLOCK提示此時再查詢時應該會出現阻塞,因為此時已有排它鎖的存在。我們先擱置疑問,在我們建立測試表時毫無疑問會對主鍵建立聚集索引,此時我們刪除聚集索引試試。

此時我們重新執行上述語句,此時將導致查詢阻塞,如下:

我們簡短的解釋一下,如果我們對錶建立了聚集索引或非聚集索引此時排它鎖將消失代替的則是RangeS-U鎖,所以當我們未新增聚集索引排它鎖則存在導致查詢阻塞,有關RangeS-S,RangeS-U,RangeX-X,RangeI-N我們將深入研究。所以上述由於導致了查詢阻塞,我們結合本節所學內容,我們利用NOLOCK來查詢資料。

 select * from Example WITH(NOLOCK)

此時毫無疑問將能夠查詢出資料,如下:

當然除非我們意識到NOLOCK導致髒讀的問題,否則謹慎用。

實戰拓展

關於NOLOCK和UPDLOCK以及HOLDLOCK則沒有什麼可講的,我們來講講UPDLOCK和READPAST,通過UPDLOCK和READPAST的結合我們能夠解決許多問題,比如我當前專案中對於更新預約人數,則用到了UPDLOCK和READPAST,因為考慮到併發如果固定預約人數為100,那麼當出現併發時將有可能導致預約超出的情況,利用UPDLOCK則可以解決其他程序過來時對其進行修改的情況,同時結合READPAST解決髒讀,同時不會阻塞,當有請求過來時我們直接利用表變數對預約人數進行更新,若更新失敗我們再進行回滾,算是一個解決方案。同時利用UPDLOCK和READPAST還可以解決其他問題,比如,當有多個併發時我們要根據篩選條件獲取第一值,也就是說第二個請求過來時獲取到的值是下一個,那麼這樣的問題該如何處理呢,若我們只是簡單進行處理,那麼第二個請求同時過來時可能也會讀取到之前讀取的那個值,基於此場景,我們可以利用UPDLOCK和READPAST來解決。我們看如下程式碼就可以理解。

DECLARE @Next INTEGER
BEGIN TRANSACTION

-- 找到下一個滿足條件的值
SELECT TOP 1 @Next = Id
FROM Test WITH (UPDLOCK, READPAST)
WHERE Flag = 0
ORDER BY Id ASC

--若找到利用標識更新,防止下一次被讀取到
IF (@Next IS NOT NULL)
    BEGIN
        UPDATE Test
        SET Flag = 1
        WHERE Id = @Next
    END

COMMIT TRANSACTION

-- 返回我們查詢到的值
IF (@Next IS NOT NULL)
    SELECT * FROM Test WHERE Id = @Next

當然上述可以避免阻塞,我們也可以在阻塞的情況下來處理利用ROWLOCK和HOLDLOCK來解決

BEGIN TRAN

SELECT 
FROM Test
WITH (HOLDLOCK, ROWLOCK)
WHERE Id = 1

--TODO

COMMIT TRAN

總結

本節我們講述了博主比較疑惑的幾種鎖例如READPAST,之前未接觸過,專案中在老大的指導下才知道,本來打算今天結束SQL Server基礎系列,誰知中途學習時遇到了其他問題,比如還有其他四種鎖型別,我還得再研究研究,真的是SQL Server基礎系列最後一篇,真的不騙你,同時.NET Core也會不定時更新,歡迎大家繼續關注部落格