# 概述
并發訪問控制作為數據庫領域非常重要的一個問題,目前已經有比較成熟的解決方案,這里介紹三種。
* 悲觀并發控制
* 樂觀并發控制
* 多版本并發控制
悲觀并發控制最常見,也就是我們經常說的悲觀鎖。
樂觀并發控制表示樂觀鎖,樂觀鎖不是真正的鎖,而表示了一種并發控制策略。
多版本并發控制(MVCC),不同于樂觀鎖和悲觀鎖的互斥關系,它能夠和樂觀鎖悲觀鎖任意一種結合使用,顯著提升數據庫的讀性能。
# 悲觀并發控制 - 鎖
確保資源在同一時刻只能被一個事務獨占。通過悲觀鎖來實現。
**當一個事務需要操作資源時需要先獲得該資源的鎖**,保證了在一個事務歸還鎖之前,其他事務無法獲取該資源的鎖。
## 鎖類型
* 共享鎖(讀鎖)
* 互斥鎖(寫鎖)
為了讓數據庫的并發能力最大化,數據庫設計了兩種鎖,分別是共享鎖和互斥鎖。當一個事務獲得行記錄的共享鎖時,只能對行進行讀操作。當獲取記錄行的互斥鎖時,能夠對行進行讀,寫操作。
共享鎖,互斥鎖除了能夠限制事務對資源的讀寫,相互之間還有限制關系。多個事務可以獲得同一資源的共享鎖,而只能有一個事務獲得某一資源的互斥鎖。
當事務無法獲得資源的鎖時,就會陷入阻塞狀態。直到其他事務釋放鎖。
## 加鎖方式
通過圖1可以發現,**兩階段鎖協議**的加鎖和解鎖處于兩個階段,在加鎖階段,鎖的數量逐個增加。**一次性鎖協議**在開始階段需要獲得全部數量的鎖
* 一次性鎖協議
* 事務開始時,一次性申請所有的鎖,無法獲取部分數據的鎖時,無法開啟事務
* 提交事務時釋放所有鎖
* 避免了死鎖
* 并發性不高,事務中的記錄被加鎖,從事務開始到提交
* 兩階段鎖協議
* 整個事務分為兩個階段,第一階段是從開啟事務開始,到提交事務/回滾結束,只能加鎖,不能解鎖。第二階段只能解鎖。
* 出現了死鎖
* 并發性相比一次性鎖協議有明顯提升
* 提升性能考慮,在事務中操作數據的順序,應當按照熱點升序。參考圖2的庫存表記錄

:-: 圖1

圖2
## 死鎖
悲觀鎖的在解決事務并發的同時,引入了死鎖的問題。
解決死鎖的兩種策略,
1. 預防死鎖的發生
2. 發生死鎖之后的處理
### 預防死鎖
1. 事務開始時將涉及到的所有資源原子性鎖定。
2. 搶占加事務回滾。
每個事務被開啟時,獲得數據庫分配的一個時間戳。事務之間觸發死鎖時,數據庫通過對比事務的時間戳來執行不同的方案。
* wait-die。如圖3。1 等待另一個事務釋放鎖。2 保持當前時間戳當前事務回滾。
* round-wait。如圖4。1 另一個事務立馬回滾。2 當前事務等待。

圖3

圖4
### 死鎖檢測和修復
當預防死鎖的機制無法生效時,數據庫需要有一個檢測事務是否進入死鎖狀態,并解決死鎖問題的機制。
## 鎖的粒度
根據鎖定的數據范圍將鎖粒度分為數據庫鎖,表鎖,行鎖。
* 父節點被加鎖時,所有子節點會被加上隱式鎖。避免子節點被加鎖
* 子節點被加鎖時,對應的父節點會被加上意向鎖。避免父節點被加鎖。意向鎖之前完全不會互斥
# 樂觀并發控制
樂觀并發控制也成為樂觀鎖,但跟悲觀鎖不一樣,樂觀鎖并不是真正的鎖,只是一種并發控制策略。
## 實現策略
1. 在數據中新增字段version,表示當前的數據版本,每次數據更新,version值會+1
2. 事務a讀取該數據,通過字段version獲得數據版本為1
3. 事務a寫入之前校驗數據版本是否為1,1的情況下更新數據,同時更新字段version+1;否則終止事務a,數據更新失敗。
# MVCC
多版本并發控制,InnoDB支持多種隔離級別,其中的重復讀 repeatable read 是通過MVCC實現的。
“版本“指的是事務ID(translate ID),數據庫維護累計的事務數量,每開啟一個事務,數量累加。InnoDB下數據的記錄格式,除了字段本身之外,額外維護了兩個字段
* DATA_TRX_ID:最新一次更新該數據行的事務ID
* DATA_ROLL_PTR:指向undo Log中舊版本數據的指針

## 參考資料
[淺談數據庫并發控制 - 鎖和 MVCC](https://draveness.me/database-concurrency-control)