MySQL 鎖機制存在的價值是什么?
我們都知道 MySQL 中有各種各樣的鎖,例如:表鎖、間隙鎖、意向鎖、行鎖等等。但你是否想過:為啥 MySQL 要有鎖機制的存在,它的存在是為了解決什么問題?今天我們就來聊聊這個問題。
沒有鎖的串行世界
我們先假設這樣一個場景:王五現在賬戶里沒有錢,于是向張三、李四各借 100 元,張三、李四很爽快地答應了。如果數據庫這時候是串行的,沒有并發執行的線程,那么其轉賬示意圖如下所示。

王五借款串行執行 - 示意圖
從上圖可以看到:
- 時間點 1 - 2 的時候,數據庫處理了張三的轉賬請求,讀取到王五的賬戶余額為 0,并將其余額加 100,此時王五賬戶余額為 100。
- 時間點 3 - 4 的時候,數據庫處理了李四的轉賬請求,讀取到王五的賬戶余額為 100,并將其余額加 100,此時王五賬戶余額為 200。
- 最后,在時間點 5 的時候,王五賬戶余額為 200 元。
可以看到最終王五的賬戶余額是 200 元,轉賬是沒問題的。這種數據庫訪問方式雖然能保證數據一致性,但是每次只能執行一個請求,并發訪問性能太差。
沒有鎖的并行世界
為了提高數據庫的并發訪問性能,MySQL 其實是支持多線程并發執行的。我們上面的例子,如果使用多線程并發處理,其可能存在的一種情況如下圖所示。

這種情況的處理流程可能是這樣的:
- 在時間點 1 的時候,線程 A 讀取到王五的賬戶余額為 0。
- 在時間點 2 的時候,線程 B 讀取到王五的賬戶余額為 0。
- 在時間點 3 的時候,線程 A 將王五賬戶余額加 100,此時王五賬戶余額為 100。
- 在時間點 4 的時候,線程 B 將王五賬戶余額加 100,此時王五賬戶余額為 100。
- 在時間點 5/6 的時候,線程 A、B 都將王五的余額回寫回去,王五賬戶余額為 100。
正常來說,王五最終的賬戶余額應該是 200 元,但實際上王五賬戶余額卻只有 100 元。通過分析上面的轉賬示意圖,我們會發現問題的關鍵點在于時間 4。
在這個時間點時,王五的賬戶余額應該是 100 元了,但是數據庫線程 B 還是以為王五的賬戶余額是 0 元,所以導致了最后的數據不一致。那么如何解決數據不一致的問題呢?答案就是:鎖機制。
有鎖的并行世界
實際上,對于上述的轉賬例子,在 InnoDB 中的處理流程如下圖所示。

- 在時間點 2 的時候,線程 A 讀取到王五的賬戶余額為 0。
- 在時間點 3 的時候,線程 B 讀取到王五的賬戶余額為 0。
- 在時間點 4 的時候,線程 A 將王五賬戶余額加 100,并獲取到鎖,此時王五賬戶余額為 100。
- 在時間點 5 的時候,線程 B 準備將王五賬戶余額加 100,但此時發現王五賬戶被鎖了,于是阻塞等待。
- 在時間點 6 的時候,線程 A 提交事務。
- 在時間點 7 的時候,線程 B 重新讀取王五最新的余額為 100 元,并加 100 元,最終在時間點 8 提交事務。
在時間點 4 的時候,數據庫線程 A 對王五賬號余額加鎖,告訴其他線程:我正在更新這條數據,你們其他人不要動。 在時間點 5 的時候,當數據庫線程 B 準備將對王五賬號余額做加 100 操作時,其發現已經有數據庫線程 A 在操作了,所以其將線程阻塞了。
等到數據庫線程 A 提交事務,釋放鎖之后,數據庫線程 B 獲取到對應的鎖。這時候數據庫線程 B 發現王五賬號的余額是 100 了,所以就在 100 余額的基礎上做更新,之后提交事務,最終王五賬號的余額就是 200 元。
提示:該例子只是為了粗略說明 InnoDB 是如何通過鎖解決數據一致性問題的,在一些細節上大家不必對于糾結。例如這個例子在事務隔離級別為 READ COMMIT 的時候適用,但是在 REPEATABLE READ 隔離級別時存在問題。
看到這里,相信大家已經明白:鎖的存在就是為了解決并發訪問下數據的不一致問題。而數據庫之所以要提供并發訪問,是為了提高數據庫的運行效率。





























