十三、MySQL數據庫的鎖,全局鎖、表鎖和行鎖的應用

鎖是在處理併發訪問數據時,用於定義訪問規則的數據結構。MySQL 中的鎖根據作用範圍分類有全局鎖表級鎖行鎖

全局鎖

當你需要對數據庫進行整庫備份時,爲了保證備份時刻的所有數據一致性,需要確保數據庫在備份期間不進行數據更改操作。

考慮一般情況做數據備份時,正在進行下單的業務,假設有一個下單完成商品表和已付款金額表,下單完成的操作包含在商品表添加商品和在金額表記錄是否付款
此時,操作的順序是:①備份下單商品表,②下單付款,③備份已付款金額表。若數據庫崩潰恢復數據,則導致數據不完整,也就是商品已經下單完成,但是並沒有付款數據。

因此在備份的時候,我們需要保證備份的所有數據都是同一時刻的。++其中一個方法就是開啓全局讀鎖++。

MySQL 開啓全局讀鎖命令:

Flush tables with read lock

使用這個命令將整個庫至於只讀狀態,此時數據庫更新語句(數據增刪改)、數據定義語句(建表語句,修改表等)和存在更新語句的事務的提交均會被阻塞。

使用該方法做整庫備份的缺陷:

  1. 在主庫進行備份會導致更新語句阻塞,業務停擺
  2. 在從庫備份,會導致備份期間不能執行從主庫過來的 binlog,導致主從延遲。

++方法二:使用事務來保證數據的一致性,即可重複讀隔離級別下。++

mysqldump 命令可以做邏輯備份,在使用參數 -single-transaction 時,可以開啓一個事務,保證數據的一致性。

使用事務備份的缺陷:

  1. 備份的庫中所有的表的引擎必須支持事務

表級鎖

表級鎖分兩種,一種是表鎖,一種是元數據鎖(meta data lock,MDL)。

表鎖

表鎖的語法如下:

#加鎖
lock tables ... read/write

#釋放鎖,關閉客戶端會自動釋放鎖
unlock tables

# 例如現在要給表 order 添加寫鎖,執行完操作再釋放
lock tables order read;
執行操作
unlock tables;

需要注意的是,開啓的表鎖並非只會限制其他線程操作,也會限制當前線程的操作,例如:線程 A 對錶 B 開啓讀鎖,對錶 C 開啓寫鎖,則其他線程不能寫表B不能讀寫表C,而當前線程只能讀表B,寫表C,不能做其他操作。

元數據鎖 MDL

MDL 是爲了保證讀寫數據時的正確性,例如,在讀數據時,對錶結構進行修改,增加了字段或者刪了一列,那麼讀的數據就結構不一致了。

MDL 的邏輯是在增刪改查數據時,加 MDL 讀鎖,在對錶結構修改時,加 MDL 寫鎖。

  1. MDL 讀鎖之間不互斥,即允許多個線程同時拿到 MDL 讀鎖。
  2. MDL 讀寫鎖和 MDL 寫鎖之間是互斥的,也就是有線程拿到 MDL 寫鎖後,其他線程(包括增刪改查操作和表結構修改操作)只能阻塞等待。

注意:MDL 鎖是系統默認會加的,不需要手動添加。

如何正確的修改表結構
當一個表在實際使用中總是被業務頻繁訪問時,該如何修改表才能不導致數據庫崩潰呢?

首先,數據庫崩潰的原因,是由於執行表結構修改後,若還有線程在操作該表,那麼當前線程會被阻塞等待,並且會導致後面所有的線程一起等待,當阻塞時間過長時就可能導致數據庫崩潰。

行鎖

行鎖是由數據庫引擎各自實現的,InnoDB 實現了行鎖,但 MyISAM 引擎沒有實現行鎖,因此 MyISAM 引擎的表同一時刻只支持一個線程對錶進行更新。

行鎖,顧名思義,對數據表中的行加鎖,鎖粒度比表鎖小,優勢是可以支持更大的併發量。同一行數據同一時刻只允許一個線程修改,但不同行的數據可以同時被不同線程修改。

行鎖的缺陷是會導致死鎖。

兩階段加鎖協議

事務具有四大特性,即原子性,一致性,隔離性和持久性。爲了保證事務執行過程是串行化的,保證事務的隔離性,在對一行數據進行操作時,加鎖,在事務結束時釋放鎖

注意,事務執行過程中,對於一行數據,是需要修改的時候則加行鎖,但是並不是用完就釋放的,而是要到事務結束才釋放行鎖,這是爲了保證事務的隔離性。這就是兩階段鎖協議。

因此,根據兩階段鎖協議,在一個事務中,將熱點數據的操作放在最後一步將會獲得更好的性能,即熱點數據的鎖被佔有的時間越短越好。

死鎖和死鎖檢測

行鎖雖然提高了併發量提高了整體性能,但是它在某些情況下會導致死鎖,性能便會大大降低。

死鎖是指兩個線程在互相等待對方解鎖資源的現象,例如,線程 A 拿到了資源 B,線程 C 拿到了資源 D,此時線程 A 需要資源 D 才能繼續往下執行,而線程 C 需要拿到資源 B 才能繼續往下執行,此時兩個線程就走進了互相等待互不放手的死衚衕。

如何解決死鎖:

  1. 設置等待超時時間。InnoDB 參數 innodb_lock_wait_timeout 可以設置獲取資源時的等待超時時間,一旦超過時間則放棄等待並且釋放自己所持有的鎖。
  2. 發起死鎖檢測,參數 innodb_deadlock_detect 設置爲 on 時,表示開啓死鎖檢測邏輯。

InnoDB 死鎖等待時長默認是 50s,通常情況這個時長是無法忍受的,但如果把時間設置太短,則會出現誤傷情況,即不是死鎖等待的情況也被放棄等待。

因此一般情況都使用死鎖檢測策略,實際上 InnoDB 默認也是開啓這個策略的。

死鎖檢測的性能問題和解決方案

當事務在給資源加鎖時等待,就會觸發死鎖檢測動作,檢測原理是每個事務當做一個節點,當事務 A 等待事務 B 得資源時,則 A --> B,最後形成有向圖,只要檢測是否是環路即可,存在環路則表示存在死鎖。一般檢測到死鎖的情況後,對持有較少的行鎖的事務進行回滾即可。因此死鎖檢測是一個 O(n) 複雜度的操作。

考慮這樣一種場景,如果有大量線程同時要更新一行數據,則對每個線程做死鎖檢測時,總時間複雜度就是 O(n²)。即 1000 個線程併發時,死鎖檢測操作是百萬級別的。

  1. 從業務上保證不會產生死鎖,關閉死鎖檢測即可
  2. 控制每個客戶端的併發數上限,如果客戶端很多的話作用不大。
  3. 從業務上分散資源,如對一個頻繁需要被轉賬的賬戶做優化,可以設置多個賬戶,隨機分配併發請求即可。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章