SQL Server-聚焦NOLOCK、UPDLOCK、HOLDLOCK、READPAST你弄懂多少?(三十四)


前言

時間流逝比較快,博主也在快馬加鞭學習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也會不定時更新,歡迎大家繼續關注博客和公眾號。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM