SQL Server中的鎖分為兩類:
- 共享鎖
- 排它鎖
鎖的兼容性:事務間鎖的相互影響稱為鎖的兼容性。
鎖模式 | 是否可以持有排它鎖 | 是否可以持有共享鎖 |
---|---|---|
已持有排它鎖 | 否 | 否 |
已持有共享鎖 | 否 | 是 |
SQL Server中可以鎖定的資源包括:RID或鍵(行)、頁、對象(如表)、數據庫等等。
在試圖修改數據(增刪改)時,事務會請求數據資源的一個排它鎖而不考慮事務的隔離級別。排它鎖直到事務結束才會解除。對於單語句事務,語句執行完畢該事物就結束了;對於多語句事務,執行完COMMIT TRAN或者ROLLBACK TRAN命令才意味着事務的結束。
在事務持有排它鎖期間,其它事務不能修改該事物正在操作的數據行,但能否讀取這些行,則取決於事務的隔離級別。
在試圖讀取數據時,事務默認請求數據資源的共享鎖,事務結束時會釋放鎖。可以通過事務隔離級別控制事務讀取數據時鎖定的處理方式。
SQL Server中事務隔離級別分為以下兩大類:
- 基於悲觀並發控制(會話級別)的四個隔離級別(隔離級別自上而下依此增強):
- READ UNCOMMITTED
- READ COMMITTED(默認)
- REPEATABLE READ
- SERIALIZABLE
不同會話之間的隔離級別及會話嵌套間的隔離級別互不影響,詳情可參閱此處。
-
基於樂觀並發控制(數據庫級別)的兩個隔離級別(隔離級別自上而下依此增強):
- SNAPSHOT
- READ COMMITTED SNAPSHOT(默認)
可以通過下面的語句來設置會話的隔離級別:
SET TRANSACTION ISOLATION LEVEL <isolation name>
隔離級別可以確定並發用戶讀取或寫入的行為。在獲得鎖和鎖的持續期間,不能控制寫入者的行為方式,當時可以控制讀取者的行為方式。此外,也可通過控制讀取者的行為方式來隱式的影響寫入者的行為。隔離級別越高讀取者請求的鎖越強,持續時間越長,數據一致性越高,並發性越低。
READ COMMITTED SNAPSHOT
和SNAPSHOT
可以看作是READ COMMITTED
和SERIALIZABLE
對應的樂觀並發控制實現。
在事務持有一個數據資源的鎖時,若另一個事務請求該資源的不兼容鎖時,請求會被阻塞而進入等待狀態。該請求一直等待直至被鎖定的資源釋放或者等待超時。可以通過語句以下語句來查詢數據庫中事務鎖信息:
--獲取當前會話Id SELECT @@SPID; --查詢數據庫中鎖信息 SELECT * FROM sys.dm_tran_locks; --使用KILL命令關閉id為52的會話 --注意KILL命令不是SQL而是SQL Server用於管理數據庫的命令 --KILL命令會回滾事務 KILL 52;
設置鎖超時時間,鎖超時不會回滾事務:
--設置鎖超時時間為5S SET LOCK_TIMEOUT 5000; --取消超時時間限制 SET LOCK_TIMEOUT -1;
READ UNCOMMITTED
在該隔離級別中,讀取者無需請求共享鎖,從而也不會與持有排它鎖的寫入者發生沖突。如此,讀取者可以讀到寫入者尚未提交的更改。即,臟讀。
在查詢語句中READ COMMITTED
可以簡寫為NOLOCK
:
SELECT * FROM A WITH(NOLOCK)
READ COMMITTED
在該隔離級別中,讀取者必須獲取一個共享鎖以防止讀取到未提交的數據。這意味着,若有其它事務正在修改資源則讀取者必須進行等待,當寫入者提交事務后,讀取者就可以獲得共享鎖進行讀取。
該隔離級別中,事務所持有的共享鎖不會持續到事務結束,當查詢語句結束(甚至未結束)時,便釋放鎖。這意味着在同一個事物中,兩次相同數據資源的讀取之間,不會持有該資源的鎖,因此,其它事務可以在兩次讀取間隙修改資源從而導致兩次讀取結果不一致,即不可重復讀,同時該隔離級別下也會產生更新丟失問題。
REPEATABLE READ
在該隔離級別中,讀取者必須獲取共享鎖且持續到事務結束。該隔離級別獲得的共享鎖只會鎖定執行查詢語句時符合查詢條件的資源。舉例如下:
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ BEGIN TRAN SELECT * FROM A WHERE Id<10;
上述語句只會鎖定符合Id<10條件的數據行,若表中Id<10的數據有Id=2,3,4,5,6五條,那么只會鎖定這五條數據:
--阻塞 DELETE FROM A WHERE Id=2; --不會阻塞 DELETE FROM A WHERE Id=7; --阻塞 UPDATE A SET Name='' WHERE Id=2; --不會阻塞 UPDATE A SET Name='' WHERE Id=7; --不會阻塞,且新插入的數據不會被鎖定,可以執行更新和刪除操作
--這就會導致幻讀問題,可參考MySQL間隙鎖(GAP)
INSERT INTO A(Id,Name) VALUES(7,'5');
該隔離級別下可以避免更新丟失問題,但會產生幻讀,即同一事務兩次相同條件的查詢之間插入了新數據,導致第二次查詢獲取到了新的數據。
SERIALIZABLE
在該隔離級別中,讀取者必須獲取共享鎖且持續到事務結束。該隔離級別的共享鎖不僅鎖定執行查詢語句時符合查詢條件的數據行,也會鎖定將來可能用到的數據行。即,阻止可能對當前讀取結果產生影響的所有操作。
舉例如下:
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE BEGIN TRAN SELECT * FROM A WHERE Id<10;
上述語句只會鎖定符合Id<10條件的數據行,若表中Id<10的數據有Id=2,3,4,5,6五條,則:
--阻塞 DELETE FROM A WHERE Id=2; --不會阻塞 DELETE FROM A WHERE Id=7; --阻塞 UPDATE A SET Name='' WHERE Id=2; --不會阻塞 UPDATE A SET Name='' WHERE Id=7; --阻塞,這里與 REPEATABLE READ 不一樣 INSERT INTO A(Id,Name) VALUES(7,'5');
SNAPSHOT
和REAED COMMITTED SNPSHOT
是SQL Server基於行版本控制技術的隔離級別,在這兩個隔離級別中,讀取者不會獲取共享鎖。SQL Server可以在tempdb庫中存儲已提交行的之前版本。如果當前版本不是讀取者所希望的版本,那么SQL Server會提供一個較舊的版本。
SNAPSHOT
在邏輯上與SERIALIZABLE
類似;READ COMMITTED SNPSHOT
在邏輯上與READ COMMITTED
類似。這兩個隔離級別中執行DELETE
和UPDATE
語句需要復制行的版本,INSERT
語句則不需要。因此,對於更新和刪除操作的性能會有負面影響,因無需獲取共享鎖,所以讀取者的性能通常會有所改善。
SNAPSHOT
在該隔離級別中,讀取者在讀取數據時,它是確保獲得事務啟動時最近提交的可用行版本。這意味着,保證獲得的是提交后的讀取並且可以重復讀取,以及確保獲得的不是幻讀,就像是在SERIALIZABLE
級別中一樣。但該隔離級別並不會獲取共享鎖。
啟用該隔離級別需要先執行下面的語句:
--需要在數據庫級別啟用基於快照的隔離級別 ALTER DATABASE DbName SET ALLOW_SNAPSHOT_ISOLATION ON;
--修改數據不提交事務 BEGIN TRAN UPDATE A SET Name='22' WHERE Id=2;
SET TRANSACTION ISOLATION LEVEL SNAPSHOT; --查詢不會被阻塞 --上述事務提交之前返回可用的舊版本,提交后則返回修改后的結果 SELECT * FROM xfh.[Table] WHERE Id=2;
沖突檢測
該隔離級別的事務中,SQL Server會進行沖突檢測以防止更新沖突,這里的檢測不會引起死鎖問題。即,若該隔離級別的事務在修改數據時,若發現已有其它事務修改了相同版本號的數據,則會引發下面的錯誤:
消息 3960,級別 16,狀態 2,第 4 行
快照隔離事務由於更新沖突而中止。您無法在數據庫'Test'中使用快照隔離來直接或間接訪問表 'A',
以便更新、刪除或插入已由其他事務修改或刪除的行。請重試該事務或更改 update/delete 語句的隔離級別。
READ COMMITTED SNPSHOT
該隔離級別與SNAPSHOT
的不同之處在於,讀取者獲得是語句啟動時(不是事務啟動時)可用的最后提交的行版本。
啟用該隔離級別需要先執行下面的語句:
--需要在數據庫級別啟用基於快照的隔離級別 --要保證執行該語句的鏈接必須是目標數據庫的唯一鏈接 ALTER DATABASE Test SET READ_COMMITTED_SNAPSHOT ON;
隔離級別 | 允許臟讀? | 允許不可重復讀? | 允許丟失更新? | 允許幻讀? | 檢測更新沖突? | 使用行版本控制? |
---|---|---|---|---|---|---|
READ UNCOMMITTED | 是 | 是 | 是 | 是 | 否 | 否 |
READ COMMITTED | 否 | 是 | 是 | 是 | 否 | 否 |
REPEATABLE READ | 否 | 否 | 否 | 是 | 否 | 否 |
SERIALIZABLE | 否 | 否 | 否 | 否 | 否 | 否 |
SNAPSHOT | 否 | 否 | 否 | 否 | 是 | 是 |
READ COMMITTED SNAPSHOT | 否 | 是 | 是 | 是 | 否 | 是 |
死鎖
對於死鎖,SQL Server會自行清理。默認情況下,SQL Server會選擇終止工作量少的事務以解除死鎖,因為工作量少便於事務的回滾操作。用戶也可以設置死鎖優先級DEADLOCK_PRIORITY
,這樣優先級低的便被終止,而不管其工作量大小。
結語
SQL Server中提供了四種不依賴行版本控制的事務隔離級別,及兩種依賴行版本控制的事務隔離級別。不同事務的隔離級別會對數據查詢語句的執行過程(是否獲取共享鎖,語句是否會被阻塞)及結果(是否有臟讀、幻讀等)產生較大的影響,對於修改數據行為的影響僅限於是否會阻塞語句的執行,因為修改數據的語句必須要獲取排它鎖才能被執行。
以上是自己《SQL Server2012 T-SQL基礎教程》事務與並發處理一章的讀書筆記,錯誤之處望各位多多指教。
推薦閱讀
數據庫村的旺財和小強
sql server鎖知識及鎖應用
數據庫兩大神器【索引和鎖】
SET TRANSACTION ISOLATION LEVEL (Transact-SQL)
漫話:MySQL中的行級鎖,表級鎖,頁級鎖
書目推薦
