前言
[轉發自: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
為了加深記憶,又轉發一篇博客,如下:
轉發至:https://www.cnblogs.com/sthinker/p/5922967.html
前段時間**公司DBA來我們這培訓。講了一大堆MYSQL的優化。 QA環節一程序員問“SQL語句中的 with nolock 除了不鎖表外,是否能讀其他鎖住的數據"。
講課的人嘟嘟了半天沒解釋清楚(有可能是MYSQL里沒有這個機制),公司的另一程序員給出了一個很簡潔明了的回答:
WITH NOLOCK 除了本身不鎖表(不加任何鎖) 也不會受其他的已存在的鎖影響, 鎖住的行數據也照樣讀,
個人認為這句話說得很清楚明了,一句話就能說明白的事,不過好奇怪的是程序員經常用這個語句竟然也不去試一下。 這里順便總結一下 其他的 SQLSERVER 中的with鎖級別:
WITH NOLOCK:無鎖
WITH HOLDLOCK:掛一個保持鎖
WITH UPDLOCK:掛一個更新鎖
WITH XLOCK:掛一個排他鎖
需要注意的是 with nolock 是不能用於update,delete insert 這種更新語句的,說繞了。簡單的說 with nolock 只能用於select。
例如:update dbo.test with(NOLOCK) set username='wokofo' --這樣的語句是錯誤的
彈回:INSERT、UPDATE、DELETE 或 MERGE 語句的目標表不允許使用 NOLOCK 和 READUNCOMMITTED 鎖提示。
實際使用:
selecttop10*from dbo.test with(NOLOCK) selecttop10*from dbo.test with(HOLDLOCK) selecttop10*from dbo.test with(XLOCK) selecttop10*from dbo.test with(UPDLOCK) update dbo.test with(HOLDLOCK) set username='wokofo' update dbo.test with(XLOCK) set username='wokofo' update dbo.test with(UPDLOCK) set username='wokofo'
NOLOCK(不加鎖)
此選項被選中時,SQL Server 在讀取或修改數據時不加任何鎖。 在這種情況下, 用戶有可能讀取到未完成事務(Uncommited Transaction)或回滾(Roll Back)中的數據, 即所謂的“臟數據”。
HOLDLOCK(保持鎖)
此選項被選中時,SQL Server 會將此共享鎖保持至整個事務結束,而不會在途中釋放。
UPDLOCK(修改鎖)
此選項被選中時,SQL Server 在讀取數據時使用修改鎖來代替共享鎖, 並將此鎖保持至整個事務或命令結束。使用此選項能夠保證多個進程能同時讀取數據但只有該進程能修改數據。
TABLOCK(表鎖)
此選項被選中時,SQL Server 將在整個表上置共享鎖直至該命令結束。 這個選項保證其他進程只能讀取而不能修改數據。
PAGLOCK(頁鎖)
此選項為默認選項, 當被選中時,SQL Server 使用共享頁鎖。
TABLOCKX(排它表鎖)
此選項被選中時,SQL Server 將在整個表上置排它鎖直至該命令或事務結束。這將防止其他進程讀取或修改表中的數據。
HOLDLOCK 持有共享鎖,直到整個事務完成,應該在被鎖對象不需要時立即釋放,等於SERIALIZABLE事務隔離級別
NOLOCK 語句執行時不發出共享鎖,允許臟讀 ,等於 READ UNCOMMITTED事務隔離級別
PAGLOCK 在使用一個表鎖的地方用多個頁鎖
READPAST 讓sql server跳過任何鎖定行,執行事務,適用於READ UNCOMMITTED事務隔離級別只跳過RID鎖,不跳過頁,區域和表鎖
ROWLOCK 強制使用行鎖
TABLOCKX 強制使用獨占表級鎖,這個鎖在事務期間阻止任何其他事務使用這個表
UPLOCK 強制在讀表時使用更新而不用共享鎖
注意: 鎖定數據庫的一個表的區別
SELECT * FROM table WITH (HOLDLOCK) 其他事務可以讀取表,但不能更新刪除
SELECT * FROM table WITH (TABLOCKX) 其他事務不能讀取表,更新和刪
總結
本節我們講述了博主比較疑惑的幾種鎖例如READPAST,之前未接觸過,項目中在老大的指導下才知道,本來打算今天結束SQL Server基礎系列,誰知中途學習時遇到了其他問題,比如還有其他四種鎖類型,我還得再研究研究,真的是SQL Server基礎系列最后一篇,真的不騙你,同時.NET Core也會不定時更新,歡迎大家繼續關注博客