阿里架構師,總結的Mysql鎖,你必須瞭解的鎖知識

今天我們來介紹一下Mysql中不同類型的鎖

數據庫鎖設計的初衷是處理併發問題。作為多用戶共享的資源,當出現併發訪問的時候,數據庫需要合理地控制資源的訪問規則。而鎖就是用來 實現這些訪問規則的重要數據結構

根據加鎖的範圍,MySQL 裡面的鎖大致可以分成全局鎖、表級鎖和行鎖三類

全局鎖

全局鎖就是對整個數據庫實例加鎖。MySQL 提供了一個加全局讀鎖的方法, 命令是 Flush tables with read lock (FTWRL)。當你需要讓整個庫處於只讀狀態的時候,可以使用這個命令,之後其他線程的以下語句會被阻塞:數據更新語句(數據的增刪改)、數據定義語句(包括建表、修改表結構等)和更新類事務的提交語句

全局鎖的典型使用場景是,做全庫邏輯備份

但是讓整庫都只讀,聽上去就很危險:

如果你在主庫上備份,那麼在備份期間都不能執行更新,業務基本上就得停擺; 如果你在從庫上備份,那麼備份期間從庫不能執行主庫同步過來的 binlog,會導致主從 延遲

官方自帶的邏輯備份工具是 mysqldump。當 mysqldump 使用參數–single-transaction 的時候,導數據之前就會啟動一個事務,來確保拿到一致性視圖。而由於 MVCC 的支持, 這個過程中數據是可以正常更新的

有了這個功能,為什麼還需要 FTWRL 呢?一致性讀是好,但前提是引擎 要支持這個隔離級別。比如,對於 MyISAM 這種不支持事務的引擎,如果備份過程中有更新,總是隻能取到最新的數據,那麼就破壞了備份的一致性。這時,我們就需要使用 FTWRL 命令了


阿里架構師,總結的Mysql鎖,你必須瞭解的鎖知識


表級鎖

MySQL 裡面表級別的鎖有兩種:一種是表鎖,一種是元數據鎖

表鎖的語法是 lock tables ... read/write。與 FTWRL 類似,可以用 unlock tables 主動 釋放鎖,也可以在客戶端斷開的時候自動釋放。需要注意,lock tables 語法除了會限制別 的線程的讀寫外,也限定了本線程接下來的操作對象

另一類表級的鎖是 MDL(metadata lock)。MDL 不需要顯式使用,在訪問一個表的時 候會被自動加上。MDL 的作用是,保證讀寫的正確性。你可以想象一下,如果一個查詢正 在遍歷一個表中的數據,而執行期間另一個線程對這個表結構做變更,刪了一列,那麼查 詢線程拿到的結果跟表結構對不上,肯定是不行的

因此,在 MySQL 5.5 版本中引入了 MDL,當對一個表做增刪改查操作的時候,加 MDL 讀鎖;當要對錶做結構變更操作的時候,加 MDL 寫鎖

讀鎖之間不互斥,因此你可以有多個線程同時對一張表增刪改查。 讀寫鎖之間、寫鎖之間是互斥的,用來保證變更表結構操作的安全性。因此,如果有兩個線程要同時給一個表加字段,其中一個要等另一個執行完才能開始執行

事務中的 MDL 鎖,在語句執行開始時申請,但是語句結束後並不會 馬上釋放,而會等到整個事務提交後再釋放

如何安全地給表加字段?

  • 首先我們要解決長事務,事務不提交,就會一直佔著 MDL 鎖
  • 比較理想的機制是,在 alter table 語句裡面設定等待時間,如果在這個指定的等待時間裡面能夠拿到 MDL 寫鎖最好,拿不 到也不要阻塞後面的業務語句,先放棄
阿里架構師,總結的Mysql鎖,你必須瞭解的鎖知識

行鎖

MySQL 的行鎖是在引擎層由各個引擎自己實現的。但並不是所有的引擎都支持行鎖,比 如 MyISAM 引擎就不支持行鎖。不支持行鎖意味著併發控制只能使用表鎖,對於這種引擎的表,同一張表上任何時刻只能有一個更新在執行,這就會影響到業務併發度。InnoDB 是支持行鎖的,這也是 MyISAM 被 InnoDB 替代的重要原因之一

兩階段鎖

在 InnoDB 事務中,行鎖是在需要的時候才加上的,但並不是不需要了就立刻 釋放,而是要等到事務結束時才釋放。這個就是兩階段鎖協議

如果你的事務中需要鎖多個行,要把最可能造成鎖衝突、最可能影響併發度的鎖儘量往後放

死鎖和死鎖檢測

當併發系統中不同線程出現循環資源依賴,涉及的線程都在等待別的線程釋放資源時,就會導致這幾個線程都進入無限等待的狀態,稱為死鎖

當出現死鎖以後,有兩種策略:

一種策略是,直接進入等待,直到超時。這個超時時間可以通過參數 innodb_lock_wait_timeout 來設置。 另一種策略是,發起死鎖檢測,發現死鎖後,主動回滾死鎖鏈條中的某一個事務,讓其 他事務得以繼續執行。將參數 innodb_deadlock_detect 設置為 on,表示開啟這個邏輯

正常情況下我們還是要採用第二種策略,即:主動死鎖檢測,而且 innodb_deadlock_detect 的默認值本身就是 on。主動死鎖檢測在發生死鎖的時候,是 能夠快速發現並進行處理的,但是它也是有額外負擔的

每當一個事務被鎖的時候,就要看看它所依賴的線程有沒有被別人鎖住,如此循環,最後判斷是否出現了循環等待,也就是死鎖

那如果是我們上面說到的所有事務都要更新同一行的場景呢?

每個新來的被堵住的線程,都要判斷會不會由於自己的加入導致了死鎖,這是一個時間複雜度是 O(n) 的操作。假設有 1000 個併發線程要同時更新同一行,那麼死鎖檢測操作就是100萬這個量級的。雖然最終檢測的結果是沒有死鎖,但是這期間要消耗大量的 CPU 資源

怎麼解決由這種熱點行更新導致的性能問題呢?

一個思路是控制併發度。根據上面的分析,你會發現如果併發能夠控制住,比如同一行 同時最多隻有 10 個線程在更新,那麼死鎖檢測的成本很低,就不會出現這個問題

這個併發控制要做在數據庫服務端。如果你有中間件,可以考慮在中間件實現;如 果你的團隊有能修改 MySQL 源碼的人,也可以做在 MySQL 裡面。基本思路就是,對於 相同行的更新,在進入引擎之前排隊

還可以考慮通過將一行改成邏輯上的多行來減少鎖衝突


分享到:


相關文章: