時間流逝比較快,博主也在馬不停蹄學習SQL Server,下班回來再晚也不忘記更新下博客,時間擠擠總會有的,如今的努力求的是將來所謂的安穩,每學一門爲的是深度而不是廣度,求的是知識自成體系而不是零散,廢話很少說本節咱們來說講SQL Server基礎系列最後幾節內容,這話博主說了n次,呵呵。數據庫
隨便翻翻博客園對於各類鎖的介紹真的是一個字【多】,僅僅介紹其概念,再要麼就是轉載其概念,不知道那些轉載概念的園友是否已經弄懂了,稍微發下感慨。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指定提示時此時SQL Server數據庫引擎在返回結果集時將不會返回鎖定的行或者數據頁。它除了和NOLOCK同樣不會致使查詢阻塞外,由於不會返回鎖定的行記錄因此其優勢好包括不存在髒讀。可是其缺點則是由於不包含鎖定的行記錄可是很難保證結果集或者修改語句是否包含咱們所必須須要返回的行。有可能在咱們的業務邏輯中,須要返回咱們必須須要的行。它的使用方式和NOLOCK同樣,下面咱們來看下實際例子,更新測試表中的SalePrice列,以下:spa
BEGIN TRANSACTION UPDATE TOP(1) Example SET SalePrice = SalePrice + 1
因爲咱們並未提交或者回滾事務因此此時更新的數據行已經被影響,下面咱們利用READPAST提示來查詢表中總數據行。翻譯
SELECT COUNT(*)
FROM Example WITH(READPAST)
在咱們的測試表中數據行爲300條,同時咱們進行了上述更新,當咱們利用READPAST提示進行查詢總數據行時,由於更新而未提交或者回滾致使此時有一行記錄被排它鎖鎖住,而READPAST的做用則是跳過鎖住的行,因此此時很明顯只返回299條數據,以下:code
經過上述圖顯示因爲更新數據行被鎖定,因此此時利用READPAST來查詢總數據行時致使更新數據行將被忽略。
怎麼會出現一個更新鎖的呢,原來咱們對於查詢和更新死鎖說到了排它鎖,這個排它鎖和更新鎖不是同樣的麼,此言差矣,容我娓娓道來,這個UPDLOCK只是針對於表中的某一行記錄來鎖定從而阻止其餘操做對該行的數據更新,說到這裏想必咱們已經明瞭,UPDLOCK是行級別,而排它鎖則是表級別,兩者不可同日而語。也就說當咱們對某一行添加UPDLOCK提示時並不會阻塞其餘查詢操做,下面咱們來看看,咱們打開一個窗口來更新測試表中篩選條件爲SaleID等於1的記錄並用UPDLOCK鎖住。
BEGIN TRAN select * from Example WITH (UPDLOCK) where SaleID = 1
此時咱們再來開一個窗口進行查詢,以下:
select * from Example
此時咱們將看到可以查詢出全部數據,以下:
這個又是什麼玩意了,根據詞達意翻譯爲厚住鎖【哈哈】,這個翻譯雖然有點勉強,可是很是明確的表達了其意思,有點強制性的意味,當咱們使用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也會不定時更新,歡迎你們繼續關注博客和公衆號。