MySQL實戰45講學習筆記:第六講

1、今日內容概要

今天我要跟你聊聊 MySQL 的鎖。數據庫鎖設計的初衷是處理併發問題。做爲多用戶共享的資源,當出現併發訪問的時候,數據庫須要合理地控制資源的訪問規則。而鎖就是用來
實現這些訪問規則的重要數據結構。mysql

根據加鎖的範圍,MySQL 裏面的鎖大體能夠分紅全局鎖、表級鎖和行鎖三類。今天這篇文章,我會和你分享全局鎖和表級鎖。而關於行鎖的內容,我會留着在下一篇文章中再和
你詳細介紹。sql

這裏須要說明的是,鎖的設計比較複雜,這兩篇文章不會涉及鎖的具體實現細節,主要介紹的是碰到鎖時的現象和其背後的原理。數據庫

2、全局鎖

一、什麼是全局鎖安全

顧名思義,全局鎖就是對整個數據庫實例加鎖。
MySQL 提供了一個加全局讀鎖的方法,命令是 Flush tables with read lock (FTWRL)。當你須要讓整個庫處於只讀狀態的時候,可使用這個命令,
以後其餘線程的如下語句會被阻塞:bash

  • 一、數據更新語句(數據的增刪改)
  • 二、數據定義語句(包括建表、修改表結構等)
  • 三、更新類事務的提交語句。

二、全局鎖的典型使用場景?session

全局鎖的典型使用場景是,作全庫邏輯備份、也就是把整庫每一個表都 select 出來存成文本。數據結構

三、加全局鎖不太好併發

之前有一種作法,是經過 FTWRL 確保不會有其餘線程對數據庫作更新,而後對整個庫作備份。注意,在備份過程當中整個庫徹底處於只讀狀態。
可是讓整庫都只讀,聽上去就很危險:工具

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

看來加全局鎖不太好。可是細想一下,備份爲何要加鎖呢?咱們來看一下不加鎖會有什麼問題。spa

四、備份爲何要加鎖?不加鎖會有什麼問題?

假設你如今要維護「極客時間」的購買系統,關注的是用戶帳戶餘額表和用戶課程表。如今發起一個邏輯備份。假設備份期間,有一個用戶,他購買了一門課程,業務邏輯裏就
要扣掉他的餘額,而後往已購課程裏面加上一門課。

若是時間順序上是先備份帳戶餘額表 (u_account),而後用戶購買,而後備份用戶課程表(u_course),會怎麼樣呢?你能夠看一下這個圖:

能夠看到,這個備份結果裏,用戶 A 的數據狀態是「帳戶餘額沒扣,可是用戶課程表裏面已經多了一門課」。若是後面用這個備份來恢復數據的話,用戶 A 就發現,本身賺了。

做爲用戶可別以爲這樣可真好啊,你能夠試想一下:若是備份表的順序反過來,先備份用戶課程表再備份帳戶餘額表,又可能會出現什麼結果?

也就是說,不加鎖的話,備份系統備份的獲得的庫不是一個邏輯時間點,這個視圖是邏輯不一致的。

五、mysqldump備份過程當中數據是能夠正常更新的

說到視圖你確定想起來了,咱們在前面講事務隔離的時候,實際上是有一個方法可以拿到一致性視圖的,對吧?

是的,就是在可重複讀隔離級別下開啓一個事務。

備註:若是你對事務隔離級別的概念不是很清晰的話,能夠再回顧一下第 3篇文章《事務隔離:爲何你改了我還看不見?》中的相關內容。

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

六、有了mysqldump這個功能,爲何還須要 FTWRL 呢?

一致性讀是好,但前提是引擎要支持這個隔離級別。好比,對於 MyISAM 這種不支持事務的引擎,若是備份過程當中有更新,老是隻能取到最新的數據,
那麼就破壞了備份的一致性。這時,咱們就須要使用FTWRL 命令了。

因此,single-transaction 方法只適用於全部的表使用事務引擎的庫。若是有的表使用了不支持事務的引擎,那麼備份就只能經過 FTWRL 方法。
這每每是 DBA 要求業務開發人員使用 InnoDB 替代 MyISAM 的緣由之一。

七、既然要全庫只讀,爲何不使用 set global readonly=true 的方式呢?

你也許會問,既然要全庫只讀,爲何不使用 set global readonly=true 的方式呢?確實 readonly 方式也可讓全庫進入只讀狀態,但我仍是會建議你用 FTWRL 方式,主要
有兩個緣由:

  • 一是,在有些系統中,readonly 的值會被用來作其餘邏輯,好比用來判斷一個庫是主庫仍是備庫。所以,修改 global 變量的方式影響面更大,我不建議你使用。
  • 二是,在異常處理機制上有差別。若是執行 FTWRL 命令以後因爲客戶端發生異常斷開,那麼 MySQL 會自動釋放這個全局鎖,整個庫回到能夠正常更新的狀態。而將整個 庫設置爲 readonly 以後,若是客戶端發生異常,則數據庫就會一直保持 readonly 狀態,這樣會致使整個庫長時間處於不可寫狀態,風險較高。

業務的更新不僅是增刪改數據(DML),還有多是加字段等修改表結構的操做(DDL)。不管是哪一種方法,一個庫被全局鎖上之後,你要對裏面任何一個表作加字段操做,都是會被鎖住的。

可是,即便沒有被全局鎖住,加字段也不是就能一路順風的,由於你還會碰到接下來咱們要介紹的表級鎖。

3、表級鎖

MySQL 裏面表級別的鎖有兩種:一種是表鎖,一種是元數據鎖(meta data lock,MDL)

一、表鎖

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

舉個例子, 若是在某個線程 A 中執行 lock tables t1 read, t2 write; 這個語句,則其餘線程寫 t一、讀寫 t2 的語句都會被阻塞。同時,線程 A 在執行 unlock tables 以前,也只能執
行讀 t一、讀寫 t2 的操做。連寫 t1 都不容許,天然也不能訪問其餘表。

在尚未出現更細粒度的鎖的時候,表鎖是最經常使用的處理併發的方式。而對於 InnoDB 這種支持行鎖的引擎,通常不使用 lock tables 命令來控制併發,畢竟鎖住整個表的影響面
仍是太大。

二、元數據鎖

另外一類表級的鎖是 MDL(metadata lock)。MDL 不須要顯式使用,在訪問一個表的時

候會被自動加上。MDL 的做用是,保證讀寫的正確性。你能夠想象一下,若是一個查詢正在遍歷一個表中的數據,而執行期間另外一個線程對這個表結構作變動,刪了一列,那麼查
詢線程拿到的結果跟表結構對不上,確定是不行的。

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

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

三、給一個小表加個字段,致使整個庫掛了。

雖然 MDL 鎖是系統默認會加的,但倒是你不能忽略的一個機制。好比下面這個例子,我常常看到有人掉到這個坑裏:給一個小表加個字段,致使整個庫掛了

你確定知道,給一個表加字段,或者修改字段,或者加索引,須要掃描全表的數據。在對大表操做的時候,你確定會特別當心,以避免對線上服務形成影響。而實際上,即便是小
表,操做不慎也會出問題。咱們來看一下下面的操做序列,假設表 t 是一個小表。

備註:這裏的實驗環境是 MySQL 5.6。

咱們能夠看到 session A 先啓動,這時候會對錶 t 加一個 MDL 讀鎖。因爲 session B 須要的也是 MDL 讀鎖,所以能夠正常執行。

以後 session C 會被 blocked,是由於 session A 的 MDL 讀鎖尚未釋放,而 sessionC 須要 MDL 寫鎖,所以只能被阻塞。

若是隻有 session C 本身被阻塞還沒什麼關係,可是以後全部要在表 t 上新申請 MDL 讀鎖的請求也會被 session C 阻塞。前面咱們說了,全部對錶的增刪改查操做都須要先申請
MDL 讀鎖,就都被鎖住,等於這個表如今徹底不可讀寫了。

若是某個表上的查詢語句頻繁,並且客戶端有重試機制,也就是說超時後會再起一個新session 再請求的話,這個庫的線程很快就會爆滿。

你如今應該知道了,事務中的 MDL 鎖,在語句執行開始時申請,可是語句結束後並不會立刻釋放,而會等到整個事務提交後再釋放。

基於上面的分析,咱們來討論一個問題,如何安全地給小表加字段?

四、如何安全的給小表加字段

首先咱們要解決長事務,事務不提交,就會一直佔着 MDL 鎖。在 MySQL 的information_schema 庫的 innodb_trx 表中,你能夠查到當前執行中的事務。若是你要
作 DDL 變動的表恰好有長事務在執行,要考慮先暫停 DDL,或者 kill 掉這個長事務。

但考慮一下這個場景。若是你要變動的表是一個熱點表,雖然數據量不大,可是上面的請求很頻繁,而你不得不加個字段,你該怎麼作呢?

這時候 kill 可能未必管用,由於新的請求立刻就來了。比較理想的機制是,在 alter table語句裏面設定等待時間,若是在這個指定的等待時間裏面可以拿到 MDL 寫鎖最好,拿不
到也不要阻塞後面的業務語句,先放棄。以後開發人員或者 DBA 再經過重試命令重複這個過程。

MariaDB 已經合併了 AliSQL 的這個功能,因此這兩個開源分支目前都支持 DDLNOWAIT/WAIT n 這個語法。

ALTER TABLE tbl_name NOWAIT add column ...
ALTER TABLE tbl_name WAIT N add column ... 

4、小結

今天,我跟你介紹了 MySQL 的全局鎖和表級鎖。

全局鎖主要用在邏輯備份過程當中。對於所有是 InnoDB 引擎的庫,我建議你選擇使用–single-transaction 參數,對應用會更友好。

表鎖通常是在數據庫引擎不支持行鎖的時候纔會被用到的。若是你發現你的應用程序裏有lock tables 這樣的語句,你須要追查一下,比較可能的狀況是:

  • 要麼是你的系統如今還在用 MyISAM 這類不支持事務的引擎,那要安排升級換引擎;
  • 要麼是你的引擎升級了,可是代碼還沒升級。我見過這樣的狀況,最後業務開發就是把lock tables 和 unlock tables 改爲 begin 和 commit,問題就解決了。

MDL 會直到事務提交才釋放,在作表結構變動的時候,你必定要當心不要致使鎖住線上查詢和更新。

最後,我給你留一個問題吧。備份通常都會在備庫上執行,你在用–single-transaction 方法作邏輯備份的過程當中,若是主庫上的一個小表作了一個 DDL,好比給一個表上加了一
列。這時候,從備庫上會看到什麼現象呢?

你能夠把你的思考和觀點寫在留言區裏,我會在下一篇文章的末尾和你討論這個問題。感謝你的收聽,也歡迎你把這篇文章分享給更多的朋友一塊兒閱讀。

說明:這篇文章沒有介紹到物理備份,物理備份會有一篇單獨的文章。

5、上期問題

上期的問題是關於對聯合主鍵索引和 InnoDB 索引組織表的理解。我直接貼 @老楊同志 的回覆略做修改以下(我修改的部分用橙色標出):表記錄

–a--|–b--|–c--|–d--
1 2 3 d
1 3 2 d
1 4 3 d
2 1 3 d
2 2 2 d
2 3 4 d

主鍵 a,b 的聚簇索引組織順序至關於 order by a,b ,也就是先按 a 排序,再按 b 排序,c 無序。

索引 ca 的組織是先按 c 排序,再按 a 排序,同時記錄主鍵–c--|–a--|–主鍵部分b-- (注意,這裏不是 ab,而是隻有 b)

2 1 3
2 2 2
3 1 2
3 1 4
3 2 1
4 2 3

這個跟索引 c 的數據是如出一轍的。

索引 cb 的組織是先按 c 排序,在按 b 排序,同時記錄主鍵–c--|–b--|–主鍵部分a-- (同上)

2 2 2
2 3 1
3 1 2
3 2 1
3 4 1
4 3 2

因此,結論是 ca 能夠去掉,cb 須要保留。

相關文章
相關標籤/搜索