今天咱們來介紹一下Mysql中不一樣類型的鎖mysql
數據庫鎖設計的初衷是處理併發問題。做爲多用戶共享的資源,當出現併發訪問的時候,數據庫須要合理地控制資源的訪問規則。而鎖就是用來 實現這些訪問規則的重要數據結構sql
根據加鎖的範圍,MySQL 裏面的鎖大體能夠分紅全局鎖、表級鎖和行鎖三類數據庫
全局鎖
全局鎖就是對整個數據庫實例加鎖。MySQL 提供了一個加全局讀鎖的方法, 命令是 Flush tables with read lock (FTWRL)。當你須要讓整個庫處於只讀狀態的時候,可使用這個命令,以後其餘線程的如下語句會被阻塞:數據更新語句(數據的增刪改)、數據定義語句(包括建表、修改表結構等)和更新類事務的提交語句安全
全局鎖的典型使用場景是,作全庫邏輯備份微信
可是讓整庫都只讀,聽上去就很危險:數據結構
若是你在主庫上備份,那麼在備份期間都不能執行更新,業務基本上就得停擺; 若是你在從庫上備份,那麼備份期間從庫不能執行主庫同步過來的 binlog,會致使主從 延遲併發
官方自帶的邏輯備份工具是 mysqldump。當 mysqldump 使用參數–single-transaction 的時候,導數據以前就會啓動一個事務,來確保拿到一致性視圖。而因爲 MVCC 的支持, 這個過程當中數據是能夠正常更新的app
有了這個功能,爲何還須要 FTWRL 呢?一致性讀是好,但前提是引擎 要支持這個隔離級別。好比,對於 MyISAM 這種不支持事務的引擎,若是備份過程當中有更新,老是隻能取到最新的數據,那麼就破壞了備份的一致性。這時,咱們就須要使用 FTWRL 命令了工具
表級鎖性能
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 的行鎖是在引擎層由各個引擎本身實現的。但並非全部的引擎都支持行鎖,比 如 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 裏面。基本思路就是,對於 相同行的更新,在進入引擎以前排隊
還能夠考慮經過將一行改爲邏輯上的多行來減小鎖衝突
本文分享自微信公衆號 - 會呼吸的Coder(BreathCoder)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。