mysql二级封锁协议_MySQL 行锁、两阶段锁协议、死锁以及死锁检测
行鎖
MySQL的行鎖都是在引擎層實現的,但是 MyISAM 不支持行鎖,意味著并發控制只能使用表鎖,同一張表任何時刻只能被一個更新在執行,影響到業務并發度。InnoDB 是支持行鎖的,這也是 MyISAM 被 InnoDB 替換的重要原因之一。
行鎖就是針對數據庫中表的行記錄的鎖,這很好理解,比如事務 A 更新了一行,而這時候,事務 B 也要更新一行,則必須等事務 A 的操作完成后才能更新。
兩階段鎖
先舉個例子,假設有一個表 t,主鍵是 id,其中一個字段是 k,在下面的操作中,事務 B 的 update 語句執行時,會是什么現象呢 ?說明:紅色表示事務 A,黃色表示事務 B:
這個問題的結論取決于事務 A 執行完前兩條語句后,持有哪些鎖,以及在什么時候釋放。
實際上,事務 A 持有兩個記錄的行鎖,都是在 commit 的時候才釋放的,所以事務 B 的 update 就會被阻塞,直到事務 A 執行 commit 之后,事務 B 才能被繼續執行。也就是說,在 InnoDB 事務中,行鎖是在需要的時候才加上的,但并不是不需要了就立刻釋放,需要等事務結束時才釋放,這就是兩階段鎖協議,分為加鎖階段和解鎖階段,所有的 lock 操作都在 unlock 操作之后。
假設你負責實現一個電影票在線交易業務,顧客 userA 要在影院 cinema 購買電影票,需要涉及以下操作:
扣除顧客 userA 賬戶余額
增加影院 cinema 賬戶余額
記錄一條交易日志
也就是說,完成這次交易,需要 update 兩條記錄,并 insert 一條記錄。當然為了保證交易的原子性,我們需要這三個操作放在一個事務中。與此同時,還有顧客 userB 也在影院購買電影票,那么你會怎樣安排這三個語句在事務中的順序呢?
首先發現沖突的部分是語句 2,就是兩個事務都要給 cinema 的賬戶余額增加電影票價。根據兩階段協議,不論怎么安排語句,所有的操作需要的行鎖都是在事務提交的時候才釋放的,要想使行鎖在事務中不會停留太長時間,最大程度的減少了事務之間的鎖等待,應該把語句 2 放在最后面。如下圖所示:
死鎖
如下圖所示,事務 A 在等待事務 B 釋放 id = 2 的行鎖,而事務 B 在等待 事務 A 釋放 id = 1 的行鎖,事務 A 和事務 B 在互相等待對方的資源釋放,就是進入了死鎖狀態。
在并發系統中,不同線程出現循環資源依賴,涉及的線程都在等待別的線程釋放資源時,就會導致這幾個線程進入無限等待的狀態,成為死鎖。
當進入死鎖狀態時,有下列 2 種策略:
通過 innodb_lock_wait_timeout 來設置超時時間,InnoDB 中默認值是 50s,第一個被鎖住的事務 A 等待超過 50s 才會超時退出,其他事務才能得以執行,對于在線服務來說,這個等待時間往往是無法接受的。如果設置太短 1s,可能有的事務只是簡單的鎖等待,就被退出了,會出現很多誤傷。
通過設置 innodb_deadlock_detect = on,發起死鎖檢測,發現死鎖之后主動回滾死鎖鏈條中的某一個事務,讓其他事務得以繼續執行。比如回滾事務 A,讓事務 B 繼續執行。
死鎖檢測
正常情況下使用第 2 種策略的,但是快速發現死鎖并進行處理,也是有額外負擔的。你可以想象一下這個發現死鎖的過程:每當一個事務被鎖的時候,就要看看他依賴的線程有沒有被別人鎖住,判斷是否出現了循環等待,也就是死鎖。
假設有 1000 個并發線程,都要同時更新同一行,第 1 個線程來的時候檢測數是 0;第 2 個線程來的時候,需要檢測【線程1】有沒有被別人鎖住;第 3 個線程來的時候,需要檢測【線程1,線程2】有沒有被其他線程鎖住,以此類推,第 n 個線程來的時候,檢測數是 n - 1,所以總的檢測數是 0 + 1 + 2 + 3 + 。。。+ (n - 1) = n(n -1)/2,所以時間復雜度應該是 O(n2)。
也就是 1000 個并發線程同時操作同一行,那么死鎖檢測操作就是 100 萬這個量級的,雖然最終檢測的結果是沒有死鎖,但是這期間要消耗大量的 CPU 資源,就會看到 CPU 利用率很高,但是每秒卻執行不了幾個事務。
那么怎么處理這種熱點行更新導致的性能問題呢?
如果你能確定這個業務一定不會出現死鎖,可以臨時把死鎖關掉,這種操作帶有一定風險,因為業務設計的時候一般不會把死鎖當成一個嚴重錯誤,畢竟出現死鎖了,就回滾,然后通過業務重試一般就沒有問題了,這是業務無損的,而關掉死鎖檢測意味著可能出現大量超時,這是業務有損的。
控制并發度,比如同一行最多只有 10 個線程在更新,這樣死鎖檢測的成本很低,一個直接的想法就是在客戶端做并發控制。可是如果客戶端有 600 個,即使每個客戶端控制到只有 5 個線程,匯總到數據庫服務端以后,峰值并發數也有可能達到 3000。因此這個并發控制要在服務端,比如引入中間件來實現,在進入引擎之前排隊。
將一行改成邏輯上的多行來處理,比如影院的賬戶余額等于 10 行記錄的值總和,這樣每次給影院賬戶加金額的時候,隨機選取其中一條記錄來加,沖突概率變為原來的1/10,減少鎖的等待個數,也就減少了死鎖檢測的 CPU 消耗。這個方案看上去是無損的,但是需要根據業務邏輯做詳細設計。如果賬戶余額減少,比如退票,這個時候就要考慮當一部分行記錄變為 0 的時候,代碼要有特殊處理。
————————————————
版權聲明:本文為CSDN博主「笙南」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/weixin_38118016/article/details/90271468
總結
以上是生活随笔為你收集整理的mysql二级封锁协议_MySQL 行锁、两阶段锁协议、死锁以及死锁检测的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 银行短信通知突然收不到 银行短信通知为什
- 下一篇: 黑磷概念股票