06 | 全局锁和表锁 : 给表加个字段怎么有这么多阻碍
引言
??根據加鎖的范圍,MySQL 里面的鎖大致可以分成全局鎖、表級鎖和行鎖三類。這篇文章,與你分享全局鎖和表級鎖。而關于行鎖的內容,我會留著在下一篇文章中再和你詳細介紹。
全局鎖
??全局鎖就是對整個數據庫實例加鎖。MySQL 提供了一個加全局讀鎖的方法,命令是 Flush tables with read lock(FTWRL)。當你需要讓整個庫處于只讀狀態的時候,可以使用這個命令,之后其他線程的以下語句會被阻塞:數據更新語句(數據的增刪改)、數據定義語句(包括建表、修改表結構等)和更新類事務的提交語句。
??全局鎖的典型使用場景是,做全庫邏輯備份。也就是把整庫每個表都 select 出來存成文本。
??官方自帶的邏輯備份工具是 mysqldump。當 mysqldump 使用參數–single-transaction的時候,導數據之前就會啟動一個事務,來確保拿到一致性視圖。而由于MVCC 的支持,這個過程中數據是可以正常更新的。
??你也許會問,既然要全庫只讀,為什么不使用 set global readonly=true 的方式呢?確實 readonly 方式也可以讓全庫進入只讀狀態,但我還是會建議你用 FTWRL 方式,主要有兩個原因:
表級鎖
??MySQL 里面表級別的鎖有兩種:一種是表鎖,一種是元數據鎖(meta data lock,MDL)。
??表鎖的語法是 lock tables … read/write。與 FTWRL 類似,可以用 unlock tables 主動釋放鎖,也可以在客戶端斷開的時候自動釋放。需要注意,lock tables 語法除了會限制別的線程的讀寫外,也限定了本線程接下來的操作對象。
??舉個例子, 如果在某個線程 A 中執行 lock tables t1 read, t2 write; 這個語句,則其他線程寫 t1、讀寫 t2 的語句都會被阻塞。同時,線程 A 在執行 unlock tables 之前,也只能執行讀 t1、讀寫 t2 的操作。連寫 t1 都不允許,自然也不能訪問其他表。
??另一類表級的鎖是 MDL(metadata lock)。MDL 不需要顯式使用,在訪問一個表的時候會被自動加上。MDL 的作用是,保證讀寫的正確性。你可以想象一下,如果一個查詢正在遍歷一個表中的數據,而執行期間另一個線程對這個表結構做變更,刪了一列,那么查詢線程拿到的結果跟表結構對不上,肯定是不行的。
??因此,在 MySQL 5.5 版本中引入了 MDL,當對一個表做增刪改查操作的時候,加 MDL讀鎖;當要對表做結構變更操作的時候,加 MDL 寫鎖。
- 讀鎖之間不互斥,因此你可以有多個線程同時對一張表增刪改查。讀寫鎖之間、
- 寫鎖之間是互斥的,用來保證變更表結構操作的安全性。
因此,如果有兩個線程要同時給一個表加字段,其中一個要等另一個執行完才能開始執行。
??雖然 MDL 鎖是系統默認會加的,但卻是你不能忽略的一個機制。比如下面這個例子,我經常看到有人掉到這個坑里:給一個小表加個字段,導致整個庫掛了。
??你肯定知道,給一個表加字段,或者修改字段,或者加索引,需要掃描全表的數據。在對大表操作的時候,你肯定會特別小心,以免對線上服務造成影響。而實際上,即使是小表,操作不慎也會出問題。
我們來看一下下面的操作序列,假設表 t 是一個小表。
??基于上面的分析,我們來討論一個問題,如何安全地給小表加字段?
??首先我們要解決長事務,事務不提交,就會一直占著 MDL 鎖。在 MySQL 的information_schema 庫的 innodb_trx 表中,你可以查到當前執行中的事務。如果你要做DDL 變更的表剛好有長事務在執行,要考慮先暫停 DDL,或者 kill 掉這長事務。
??但考慮一下這個場景。如果你要變更的表是一個熱點表,雖然數據量不大,但是上面的請求很頻繁,而你不得不加個字段,你該怎么做呢?
??這時候 kill 可能未必管用,因為新的請求馬上就來了。比較理想的機制是,在 alter table語句里面設定等待時間,如果在這個指定的等待時間里面能夠拿到 MDL 寫鎖最好,拿不到也不要阻塞后面的業務語句,先放棄。之后開發人員或者 DBA 再通過重試命令重復這個過程。
總結
以上是生活随笔為你收集整理的06 | 全局锁和表锁 : 给表加个字段怎么有这么多阻碍的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 05丨深入浅出索引(下)
- 下一篇: 07丨行锁功过:怎么减少行锁对性能的影响