鎖:我們知道,最常用的處理多用戶並發訪問的方法是加鎖。當一個用戶鎖住數據庫中的某個對象時,其他用戶就不能再訪問該對象。加鎖對並發訪問的影響體現在鎖的粒度上。比如,放在一個表上的鎖限制對整個表的並發訪問;放在數據頁上的鎖限制了對整個數據頁的訪問;放在行上的鎖只限制對該行的並發訪問。可見行鎖粒度最小,並發訪問最好,頁鎖粒度最大,表鎖介於2者之間。
鎖有兩種:悲觀鎖和樂觀鎖。悲觀鎖假定其他用戶企圖訪問或者改變你正在訪問、更改的對象的概率是很高的,因此在悲觀鎖的環境中,在你開始改變此對象之前就將該對象鎖住,並且直到你提交了所作的更改之后才釋放鎖。悲觀的缺陷是不論是頁鎖還是行鎖,加鎖的時間可能會很長,這樣可能會長時間的限制其他用戶的訪問,也就是說悲觀鎖的並發訪問性不好。與悲觀鎖相反,樂觀鎖則認為其他用戶企圖改變你正在更改的對象的概率是很小的,因此樂觀鎖直到你准備提交所作的更改時才將對象鎖住,當你讀取以及改變該對象時並不加鎖。可見樂觀鎖加鎖的時間要比悲觀鎖短,樂觀鎖可以用較大的鎖粒度獲得較好的並發訪問性能。但是如果第二個用戶恰好在第一個用戶提交更改之前讀取了該對象,那么當他完成了自己的更改進行提交時,數據庫就會發現該對象已經變化了,這樣,第二個用戶不得不重新讀取該對象並作出更改。這說明在樂觀鎖環境中,會增加並發用戶讀取對象的次數。
從數據庫廠商的角度看,使用樂觀的頁鎖是比較好的,尤其在影響很多行的批量操作中可以放比較少的鎖,從而降低對資源的需求提高數據庫的性能。再考慮聚集索引。在數據庫中記錄是按照聚集索引的物理順序存放的。如果使用頁鎖,當兩個用戶同時訪問更改位於同一數據頁上的相鄰兩行時,其中一個用戶必須等待另一個用戶釋放鎖,這會明顯地降低系統的性能。interbase和大多數關系數據庫一樣,采用的是樂觀鎖,而且讀鎖是共享的,寫鎖是排他的。可以在一個讀鎖上再放置讀鎖,但不能再放置寫鎖;你不能在寫鎖上再放置任何鎖。鎖是目前解決多用戶並發訪問的有效手段。
死鎖:當二或多個工作各自具有某個資源的鎖定,但其它工作嘗試要鎖定此資源,而造成工作永久封鎖彼此時,會發生死鎖。例如:
1. 事務 A 取得數據列 1 的共享鎖定。
2. 事務B 取得數據列 2 的共享鎖定。
3. 事務A 現在要求數據列 2 的獨占鎖定,但會被封鎖直到事務B 完成並釋出對數據列 2 的共享鎖定為止。
4. 事務B 現在要求數據列 1 的獨占鎖定,但會被封鎖直到事務A 完成並釋出對數據列 1 的共享鎖定為止。
等到事務B 完成后,事務A 才能完成,但事務B 被事務A 封鎖了。這個狀況也稱為「循環相依性」(Cyclic Dependency)。事務A 相依於事務B,並且事務B 也因為相依於事務A 而封閉了這個循環。
例如以下操作就會產生死鎖,兩個連接互相阻塞對方的update。
連接1:
begin tran
select * from customers
update customers set CompanyName = CompanyName
waitfor delay '00:00:05'
select * from Employees
–因為Employees被連接2鎖住了,所以這里會阻塞。
update Employees set LastName = LastName
commit tran
連接2:
begin tran
select * from Employees
update Employees set LastName = LastName
waitfor delay '00:00:05'
select * from customers
--因為customers被連接1鎖住了,所以這里會阻塞。
update customers set CompanyName = CompanyName
commit tran
SQL Server遇到死鎖時會自動殺死其中一個事務,而另一個事務會正常結束(提交或回滾)。
SQL Server對殺死的連接返回錯誤代碼是1205,異常提示是:
Your transaction (process ID #52) was deadlocked on {lock | communication buffer | thRead} resources with another process and has been chosen as the deadlock victim. Rerun your transaction.