一:鎖的概念
按照寫技術部落格的套路,應該對鎖的概念做一個介紹,我又想,能點選進入本篇部落格的同學,想必都是聽說過鎖的。所以我儘量用簡練的語言來表述一下。
鎖的定義:鎖主要用於多使用者環境下,保證資料庫完整性和一致性的技術。
鎖的解釋:當多個使用者併發地存取資料時,在資料庫中就會產生多個事務同時存取同一資料的情況。若對併發操作不加控制就可能會讀取和儲存不正確的資料,破壞資料庫的完整性和一致性。當事務在對某個資料物件進行操作前,先向系統發出請求,對其加鎖。加鎖後事務就對該資料物件有了一定的控制。
二:鎖的分類
鎖的概念非常簡單,簡單的來用幾句話就能描述它的用途。但是鎖的分類,就明顯要複雜一些了。
鎖的分類,在教材上,網路上好多都是按兩個維度來描述的。一種維度是按鎖的功能來劃分,一種維度是按概念來劃分。09年的時候,我做了一個資料庫的培訓教程,把鎖的分類給截出來擺一下。
時隔了幾年,看起來PPT看起來很粗糙。與我這些PPT模板沒法比,但是內容仍然經典。
三:鎖的關鍵字
共享鎖,排它鎖這樣的鎖,資料庫引擎會自動管理和優化,平時寫SQL的時候,很少有去關心鎖的關鍵字。
但是今天是抱著學習的態度來看部落格的,所以必須得把這幾個關鍵字都用一篇。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
SELECT * FROM AppLog WITH (HOLDLOCK) /*共享鎖*/ SELECT * FROM AppLog WITH (UPDLOCK) /*更新鎖*/ SELECT * FROM AppLog WITH (XLOCK) WHERE LogID='AA599A4E-B727-4A65-8010-00001661765E'; /*排它鎖*/ SELECT * FROM AppLog WITH (ROWLOCK) WHERE LogID='6BE2C680-0C9F-43FA-9B4E-00000A6C1CEF'; /*行鎖*/ SELECT * FROM AppLog WITH (TABLOCKX) /*大容量更新鎖*/ SELECT * FROM AppLog WITH (XLOCK,ROWLOCK) WHERE LogID='AA599A4E-B727-4A65-8010-00001661765E'; /*鎖的組合使用*/ /*XLOCK 本身是鎖住資料行的,TABLOCKX是鎖住整張表*/ SELECT * FROM AppLog WITH (NOLOCK) /*不加鎖,當一個事務回滾後,出現髒資料*/ SELECT * FROM AppLog WITH (READPAST) /*忽略掉加鎖的資料(行資料,頁資料)*/ |
四:死鎖的發生
比如現在的資料庫用兩個使用者在用,
使用者1:
1 2 3 |
BEGIN TRAN SELECT * FROM AppLog WHERE LogID = 'A10BA165-6E52-4AFB-9EA8-000000D6B90A'; UPDATE AppLog SET AppPostion = AppPostion + AppPostion WHERE LogID = 'A10BA165-6E52-4AFB-9EA8-000000D6B90A'; |
使用者2:
1 2 3 |
BEGIN TRAN SELECT * FROM AppLog WHERE LogID = 'A10BA165-6E52-4AFB-9EA8-000000D6B90A'; UPDATE AppLog SET AppPostion = AppPostion + AppPostion WHERE LogID = 'A10BA165-6E52-4AFB-9EA8-000000D6B90A'; |
比如使用者1,使用者2同時執行 SELECT,使用者1對記錄加了共享鎖,使用者2對記錄也加了共享鎖,當使用者1 SELECT 執行完畢,準備執行UPDATE的時候,根據鎖機制,使用者1的共享鎖需要升
級到排他鎖才能執行接下來的UPDATE.
在升級排他鎖前,必須等待記錄上的其它共享鎖釋放,但是因為共享鎖只有等事務結束後才釋放。因為使用者2的共享鎖不釋放而導致使用者1等(等使用者2釋放共享鎖,自己好升級成排他鎖),同理,這時也因為使用者1的共享鎖不釋放而導致使用者2等待。死鎖就發生了。
五:無鎖查詢技巧
開啟兩個查詢視窗:其中一個執行下面語句:
1 2 3 4 5 6 7 |
CREATE TABLE a ( id INT , name NVARCHAR(20) ) BEGIN TRAN INSERT a VALUES ('1','a')--開啟一個事務,而不提交也不回滾,此時insert 語句產生的排它鎖是不會釋放的 |
在另一個視窗中執行:
1 2 3 |
select COUNT(*) from a with(nolock)--無鎖查詢,會查出結果為1 select COUNT(*) from a with(readpast)--忽略所有有鎖的記錄,此時為0 |
然後執行select * from a –此時是查不出結果的,會無限地等待下去,因為排它鎖未釋放,預設查詢的共享鎖與之不相容,所以就一直等待排它鎖的釋放,才會返回結果,即使表中已有許多資料,而排它鎖只鎖了一條記錄,但是,查詢語句也要等待這一條記錄的鎖的釋放,才會返回結果。 這便是人工手動設定的因為排它鎖未釋放而導致的死鎖(不是相互等待,而是一方無盡的等待!)。