【MySQL 讀書筆記】全局鎖 | 表鎖 | 行鎖

全局鎖python

全局鎖是針對數據庫實例的直接加鎖,MySQL 提供了一個加全局鎖的方法, Flush tables with read lock 可使用鎖將整個表的增刪改操做都鎖上其中包括 ddl 語句,只容許全局讀操做。mysql

全局鎖的典型使用場景是作全庫的邏輯備份。web

不過如今使用官方自帶工具 mysqldump 使用參數 --single-transaction 的時候,導出數據以前就會啓動一個事務。來確保拿到一致性視圖。這個應該相似於在可重複讀隔離級別下啓動一個一致性事務。因爲 MVCC 的支持,這個過程當中數據能夠正常更新。sql

另外提一點不太容易遇到的, --single-transaction 既然能夠不用鎖表,爲何還須要使用全局鎖?緣由是 --single-transaction 的時候須要支持一致性讀,可是不支持事務的引擎是不支持一致性讀的。這個時候就須要 FTWRL 命令了。數據庫

還有另一種方法用來支持設置數據庫爲只讀狀態安全

set global readonly=true

這裏 丁奇 不建議這樣設置來設置數據庫爲只讀有兩個緣由session

一是,在有些系統中,readonly 的值會被用來作其餘邏輯,好比用來判斷一個庫是主庫仍是備庫。所以,修改 global 變量的方式影響面更大,我不建議你使用。併發

二是,在異常處理機制上有差別。若是執行 FTWRL 命令以後因爲客戶端發生異常斷開,那麼 MySQL 會自動釋放這個全局鎖,整個庫回到能夠正常更新的狀態。而將整個庫設置爲 readonly 以後,若是客戶端發生異常,則數據庫就會一直保持 readonly 狀態,這樣會致使整個庫長時間處於不可寫狀態,風險較高。高併發

 

表級鎖工具

MySQL 中表級別鎖有兩種:一種是普通表鎖,一種是元數據鎖(metadata lock. MDL)

表鎖的語法是 lock tables xxx read/write 一樣使用 unlock tables 來釋放鎖。經過加讀鎖咱們能夠限制其餘語句進行寫入,可是重複加讀鎖不受影響。可是當咱們加寫鎖的時候,既不能夠讀也不能夠寫。一樣在使用 unlock tables 以後能夠解除鎖定。

另一種表級鎖是 MDL 鎖(metadata lock) MDL 鎖不須要顯示的使用,在訪問一個表的時候自動就被加上了。 MDL 鎖是用來保證讀寫正確性的,當咱們對一個表在作 增刪改查操做的時候都會被加上 MDL 讀鎖。當要進行 ddl 的時候須要加 MDL 寫鎖。

MDL 讀鎖與讀鎖之間不互斥,所以咱們能夠多個線程進程對一個表進行增刪改查。

MDL 讀寫鎖之間互斥,用來保證表結構變動的安全性。所以若是有兩個線程同時要給同一個表加字段,其中一個要等另一個執行完成以後再開始執行。

 

下面咱們來看一個比較有表明性的場景 MDL 讀鎖寫鎖互斥致使表沒法讀寫被死鎖。

session A: 開始一個事務,而後查詢 t 表,這會給 t 表加上 MDL 讀鎖。(注意該事務被打開後就一直沒有結束)

session B: 查詢一個 t 表。這裏應該是 autoocommit 會自動成功。

session C: 修改表 ddl 會加 MDL 寫鎖,和 session A 的讀鎖互斥。這個時候就鎖住了表。

session D: 因爲 session C 形成了寫鎖阻塞,因此後面全部的請求都會被鎖住。

若是該表查詢頻繁,並且客戶端有重試的機制,那麼這個數據庫的查詢線程會很快被打滿。

可能在進行 web 開發的同窗會常常遇到相似的狀況。好比我在 ipython 裏面打開了一個數據庫某個表的鏈接,而後我一直沒有 commit 。就可能形成該表在加寫鎖的時候阻塞後面全部的操做。

這種事情很是常見。

 

那麼咱們如何安全的給小表加字段,首先咱們應該解決長事務或者腳本事務的問題,由於他們會一直掛讀鎖不結束。在 MySQL 的 information_schema 中的 innodb_trx 中能夠查詢到執行中的長事務,可是比較麻煩的是這個看不到很短的事務。可是每每進行 sleep 的短事務也可能由於一直沒有 commit 而致使上面的狀況出現。

這個時候就須要把對應表的 sleep 進程 kill 掉使其恢復正常。

 

行級鎖 

先來看個描述兩階段鎖的例子:

事務 A 會持有兩條記錄的行鎖,而且只會在 commit 以後纔會釋放。

在 InnoDB 事務中,行鎖是在須要的時候加上,可是並非不須要就馬上釋放,而是等事務結束以後纔會釋放。這個就是兩階段鎖協議。

知道了這個設定咱們應該在長事務中把影響併發度的鎖儘可能日後放。下面的這一段的介紹比較複雜,我以爲 丁奇 講得仍是比較清楚的因此直接引用原文了。

 

假設你負責實現一個電影票在線交易業務,顧客 A 要在影院 B 購買電影票。咱們簡化一點,這個業務須要涉及到如下操做:

1. 從顧客 A 帳戶餘額中扣除電影票價;

2. 給影院 B 的帳戶餘額增長這張電影票價;

3. 記錄一條交易日誌。

也就是說,要完成這個交易,咱們須要 update 兩條記錄,並 insert 一條記錄。固然,爲了保證交易的原子性,咱們要把這三個操做放在一個事務中。那麼,你會怎樣安排這三個語句在事務中的順序呢?

試想若是同時有另一個顧客 C 要在影院 B 買票,那麼這兩個事務衝突的部分就是語句 2 了。由於它們要更新同一個影院帳戶的餘額,須要修改同一行數據。

根據兩階段鎖協議,不論你怎樣安排語句順序,全部的操做須要的行鎖都是在事務提交的時候才釋放的。因此,若是你把語句 2 安排在最後,好比按照 三、一、2 這樣的順序,那麼影院帳戶餘額這一行的鎖時間就最少。這就最大程度地減小了事務之間的鎖等待,提高了併發度。

 

死鎖和死鎖檢測 

若是出現下面的不慎操做就會發生死鎖。

事務 A 開啓事務,而且拿了 id = 1 的行鎖。

事務 B 開啓事務,拿到 id = 2 的行鎖。

事務 A 試圖去拿 id = 2 的行鎖被 block。

事務 B 試圖去拿 id = 1 的行鎖被 block。

解決死鎖 MySQL 目前有兩種策略,第二種策略的參數我沒有在 MySQL 5.6 版本中找到,在 MySQL 5.7 中找到。

1. 一種策略是,直接進入等待,直到超時。這個超時時間能夠經過參數 innodb_lock_wait_timeout 來設置。這個參數默認是 50s。

2. 另外一種策略是,發起死鎖檢測,發現死鎖後,主動回滾死鎖鏈條中的某一個事務,讓其餘事務得以繼續執行。將參數 innodb_deadlock_detect 設置爲 on,表示開啓這個邏輯。

很顯然,等待 50 s 失效在現實業務中是不切實際的。確定會形成高併發的業務大量的阻塞和 500 。因此看上去咱們能夠依賴第二種辦法?

可是第二種辦法也有反作用。

死鎖檢測會對每一個新來的被堵住的線程,都判斷會不會因爲本身的加入致使了死鎖,這是一個時間複雜度是 O(n) 的操做。假設有 1000 個併發線程要同時更新同一行,那麼死鎖檢測操做就是 100 萬這個量級的。雖然最終檢測的結果是沒有死鎖,可是這期間要消耗大量的 CPU 資源。所以,你就會看到 CPU 利用率很高,可是每秒卻執行不了幾個事務。

解決這個的方法是

1. 若是咱們能確保業務中就是不會存在死鎖的邏輯,那麼咱們能夠關閉死鎖檢測。

2. 咱們控制併發度,不讓某些業務更新這麼快。對客戶端的併發控制下來以後,死鎖檢測的效率是高的,也能夠解決這個問題。

 

 

Reference:

本讀書筆記皆來自發布在極客時間的 林曉斌(丁奇)的 MySQL 實戰45講:

極客時間版權全部: https://time.geekbang.org/ 版權全部: 

https://time.geekbang.org/column/article/69862

https://time.geekbang.org/column/article/70215

相關文章
相關標籤/搜索