在[數據庫(五),事務](http://www.cnblogs.com/dy2903/p/8438209.html)里面我們講了事務ACID屬性,事務最重要的能在異常情況的修復以及并發連接的處理上。
異常情況的修復主要通過**日志**來完成,那么并發連接的處理主要通過**鎖**。本章主要整理的是**鎖**的相關知識。
# 為什么需要鎖?
現在Bob的賬戶里面有1000塊錢,此時程序突然同時來了兩個要求,一個要把Bob的錢轉給Smith 20塊,一個要把Bob的錢轉Joe 30塊。這兩個要求一查Bob的賬戶,都發現現在Bob有1000塊,所以要求A算出現在Bob應該有980塊,要求B算出來Bob應有970。要求A的數據被要求B的數據覆蓋了。
這樣就出問題了,明明應該扣50塊錢,現在卻只是扣了30塊。
**鎖**就是用來解決這樣的并發訪問的問題。當每次訪問Bob賬戶之前,都加一個鎖,禁止別人再次訪問,只有**等待持有鎖的人來釋放**

# 悲觀鎖和樂觀鎖
## 悲觀鎖
如果事務A把Bob賬戶鎖住了,事務B自然不能操作Bob賬戶,也就是說其他線程只能在外面等待。
這種加鎖的方式就是**悲觀鎖**。它每次取讀寫數據時總認為數據會被別人修改,所以將數據加鎖,置于鎖定狀態,不讓別人訪問。
缺點是如果持有鎖的時間太長,其他用戶需要等待很長的時間。
悲觀鎖主要適用于**并發爭搶**比較嚴重的場景。
## 樂觀鎖
悲觀鎖的問題顯而易見,如果將數據加鎖了以后,其他的線程是無法訪問的,只能等待。如果持有鎖的時間太長,需要等待大量的時間。
所以我們引入了**樂觀鎖**,所謂樂觀鎖是認為一般情況下不會有太多的人修改余額,所有沒有加鎖,只有在最后更新的時候才去看是否有沖突。
那**具體怎么做呢?**
可以在日志中加上一個version(版本)字段,
- 每次**讀**的時候,不僅需要讀出余額,還需要讀出版本號。
- 等修改了余額以后,往回寫之前需要檢查一下版本號,看看與讀的時候版本號是否一樣。
- 如果不一樣,說明數據已經被改變了,所以需要放棄寫操作,重新讀取余額和版本號
- 如果一樣,則將新余額寫回去,把版本號加1 。
比如
事務1把Bob的余額減去30,此時它讀到了(Bob余額=1000,版本=1)
事務2也需要將Bob的余額減去50,他也讀到了(Bob余額=1000,版本=1)
然后事務1率先完成計算,把新的余額值970寫回了,版本 加 1 ,變成了版本2。
事務2寫回去的時候,發現最新的版本號變為2,表示之前讀的數據已經改變,所以需要**重新讀一遍**
這就是樂觀鎖,這種方式**適合于沖突不多的場景**,如果沖突很多,數據爭用激烈,會導致不斷的嘗試,反而降低了性能。

# 死鎖
## 死鎖產生的條件
如果出現如下這種情況
- 有兩個**線程**同時參與
- 這兩個線程在**不同方向**給同一個資源加鎖
- 爭搶相同的**資源**
那么很可能出現死鎖
比如事務1是Bob給Smith轉賬,事務2是Smith給Bob轉賬。
當這兩個事務單元同時發生的時候,就有問題呢。
事務單元1會先鎖定Bob,然后鎖定Smith,而事務單元2會先鎖定Smith,然后鎖定Bob
事務1會等待事務2把Bob給釋放了,而事務2在等待事務1把Smith釋放了。

## 如何解決
那么如何解決死鎖呢?最好的方法是盡可能不出現死鎖,當然很難。或者說如果鎖定時間超時了,則強行釋放,不過這種方法效率比較低,因為如果有用戶的事務本來時間就很長,則每個死鎖的檢測時間將會很長。
所以最優的方案在于**預測死鎖,**可以把**事務單元等待的鎖記錄下來**
比如下圖中,事務單元1持有"Lock Bob"的鎖,現在又在申請一把"Lock Smith"的鎖,在申請之前,可以查看同樣申請了"Lock Smith"的有哪些事務單元。明顯事務單元2也申請過這把鎖。好了,下一步是看事務單元2在申請什么鎖呢,發現它居然在申請"Lock Bob"這把鎖,而這把鎖目前由事務單元1持有。所以現在已經發現有死鎖的可能了,也就是發生了**碰撞**。所以可以提前補救。

## U鎖
下面來討論一種死鎖的情況。如下圖

事務1 Trx1
> 開始事務1
讀A(讀鎖)
A - 100(讀鎖需要升級為寫鎖)
提交事務1
事務2 Trx2
> 開始事務2
讀A(讀鎖)
A - 100(讀鎖需要升級為寫鎖)
提交事務2(解鎖)
事務1和事務2的讀鎖是可以并行的,所以讀鎖可以同時進入臨界區,但是寫鎖不能,會被擋在外面。此時事務2又發起了寫鎖。那么尷尬的局面就產生了。
事務1的寫鎖需要等事務2的讀鎖釋放資源。
事務2的寫鎖需要等待事務1的讀鎖釋放資源。
所以形成了死鎖。其實這種死鎖的形成條件非常的簡單,**只需要針對同一個數據進行讀寫**。比如說`update set A=A-1 where id = 100`如果運行多次,就會出現死鎖
解決的辦法是引入**U鎖**,可以將讀鎖直接升級為寫鎖。
對于事務1,讀以后馬上就是寫,所以直接就使用寫鎖,而不是讀鎖呢。

同理事務2也是如此。

