MySQL全局鎖表鎖&行鎖

MySQL全局鎖表鎖&行鎖

在我們開發的過程中,使用全局鎖和表鎖的場景比較少,接觸的也相對少一點,下面主要介紹一下。

全局鎖

FTWRL

全局鎖就是對整個數據庫實例加鎖,MySQL 提供了 flush tables with read lock (FTWRL) 的方式去加全局鎖。當你需要讓整個庫處於只讀狀態的時候,就可以使用這個命令了,之後所有線程的更改操作都會被阻塞。

mysqldump

mysqldump 是官方提供的備份工具,可以通過 --single-transaction 參數來啟用可重複讀隔離級別,從而可以拿到一個一致性視圖。

set global readonly = true

通過上述命令可以讓全庫進入只讀狀態,但是在開發當中,事務框架往往會利用這個參數來處理讀寫分離。所以通常情況下,還是不建議使用這種方式。

表級鎖

MySQL 的表級鎖有 2 種:表鎖 和 元數據鎖。

表鎖

表鎖可以使用 lock tables T read/write , 可以使用 unlock tables 主動釋放鎖,也可以在客戶端斷開的時候自動釋放鎖。

MDL (metadata lock)

MDL 沒有顯示的命令,當執行改表語句時,MDL 會保證讀寫的正確性。MySQL 在 5.5 版本以後引入了 MDL 鎖,當對一個表做 增刪改查 的時候,加 MDL 讀鎖;當要多表結構做變更的時候,加 MDL 寫鎖。

  1. MDL 讀鎖之間不互斥,因此可以有多個線程同時對一張表 增刪改查。
  2. MDL 讀-寫、寫-寫之間是互斥的,因此如果同時有 2 個線程給表加字段,則需要順序執行。

當我們在線上更改表字段的時候,可能會造成長時間的服務不可用。流程如下:

MySQL全局鎖表鎖&行鎖

從上面流程可以看出,當我們在更改線上表時,此時可能早上服務不可用。此時可以增加一個改表的超時時間,但是MySQL官方還沒有支持這種功能,MarialDB、AliSQL 的開源分支是可以支持的。

行鎖

兩階段鎖

當使用update 更新數據時,會對 where 條件掃描到的行加行鎖。下面看一下更新語句的執行流程:

MySQL全局鎖表鎖&行鎖

從上圖可以看出,事務 A 對 id=1 和 id=2 的行加鎖之後,事務 B 要對 id=1 加鎖的時候就會阻塞。在 InnoDB 事務中,行鎖是在需要的時候才加上的,並且在事務提交後釋放的,這就是兩階段鎖協議。所以我們在更新數據時,應儘量把容易產生併發更新的行放在事務末端執行。

死鎖

在事務對不同行加鎖的時候,就很有可能出現死鎖的情況。如下所示:

MySQL全局鎖表鎖&行鎖

上面事務A 和 事務B 都在等待對方釋放資源,從而就產生了死鎖的狀態。MySQL 有 2 中策略去解決死鎖:

  • 超時等待,可以通過 innodb_lock_wait_timeout 來設置,默認 50S。(一般不採用,因為 50S 對應用來說是不可接受的,並且這個值的設置也沒有合適的估算值)
  • 死鎖檢測,就是發現死鎖後,主動回滾其中一個事務。可以通過 innodb_deadlock_detect 設置為 on。

上面 2 種死鎖的解決方法,都是MySQL 本身提供的。我們實際開發的過程當中,往往是需要自己從業務的角度去考慮,如何規避死鎖和解決死鎖的問題。

  • 按規則加鎖。如 A 轉賬給 B,同時 B 也轉賬給 A,此時就很可能出現死鎖。但是如果我們根據 userId 的升序規則去加鎖,就不會產生死鎖的問題了。
  • 控制併發度。如支付系統中的賬戶系統,可以將總賬戶拆分成子賬戶,然後每個子賬戶是一個獨立的鎖實體。

參考:《極客時間:MySQL實戰》、《高性能MySQL》


分享到:


相關文章: