MYSQL實戰四十五講總結筆記_06、全局鎖和表鎖

前言:整理歸納,僅供個人溫習之用,請支持正版極客時間

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

1、全局鎖(對整個數據庫實例加鎖)

*MySQL 提供了一個加全局讀鎖的方法,命令是 Flush tables with read lock (FTWRL)。當你需要讓整個庫處於只讀狀態的時候,可以使用這個命令,之後其他線程的以下語句會被阻塞:數據更新語句(數據的增刪改)、數據定義語句(包括建表、修改表結構等)和更新類事務的提交語句。
 
*使用場景:全庫邏輯備份
 
*風險:1、如果在主庫備份,在備份期間不能更新,業務停擺;2、如果在從庫備份,備份期間不能執行主庫同步的binlog,導致主從延遲
 
*備份不加鎖的話,備份系統備份的得到的庫不是一個邏輯時間點,這個視圖是邏輯不一致的。
 
*怎麼拿到一致性視圖:在可重複讀隔離級別下開啓一個事務。
官方自帶的邏輯備份工具是 mysqldump。當 mysqldump 使用參數–single-transaction 的時候,導數據之前就會啓動一個事務,來確保拿到一致性視圖。而由於 MVCC 的支持,這個過程中數據是可以正常更新的。
 
*爲什麼還需要FTWRL:一致性讀是好,但前提是引擎要支持這個隔離級別。single-transaction 方法只適用於所有的表使用事務引擎的庫
 
*既然要全庫只讀,爲什麼不使用 set global readonly=true 的方式呢?
readonly 方式也可以讓全庫進入只讀狀態,但還是建議你用 FTWRL 方式,主要有兩個原因:
  • 在有些系統中,readonly 的值會被用來做其他邏輯,比如用來判斷一個庫是主庫還是備庫。因此,修改 global 變量的方式影響面更大,不建議使用
  • 在異常處理機制上有差異。如果執行 FTWRL 命令之後由於客戶端發生異常斷開,那麼 MySQL 會自動釋放這個全局鎖,整個庫回到可以正常更新的狀態。而將整個庫設置爲 readonly 之後,如果客戶端發生異常,則數據庫就會一直保持 readonly 狀態,這樣會導致整個庫長時間處於不可寫狀態,風險較高。
  • read_only=1只讀模式,不會影響slave同步複製的功能
  • read_only=1只讀模式,限定的是普通用戶進行數據修改的操作,但不會限定具有super權限的用戶的數據修改操作,除非設置super_read_only=on
 

2、表級鎖(表鎖、元數據鎖)

*表鎖的語法是 lock tables … read/write。可以用 unlock tables 主動釋放鎖,也可以在客戶端斷開的時候自動釋放。
Ps:lock tables 語法除了會限制別的線程的讀寫外,也限定了本線程接下來的操作對象。
在還沒有出現更細粒度的鎖的時候,表鎖是最常用的處理併發的方式。InnoDB引擎支持行鎖,一般不使用 lock tables 命令來控制併發,畢竟鎖住整個表的影響面還是太大。
 
*在 MySQL 5.5 版本中引入了 MDL(元數據鎖:保證讀寫的正確性),當對一個表做增刪改查操作的時候,加 MDL 讀鎖;當要對錶做結構變更操作的時候,加 MDL 寫鎖。
  • 讀鎖之間不互斥,可以有多個線程同時對一張表增刪改查
  • 讀寫鎖之間、寫鎖之間是互斥的,用來保證變更表結構操作的安全性。如果有兩個線程要同時給一個表加字段,其中一個要等另一個執行完才能開始執行。
 
*案例:給一個小表加個字段,導致整個庫掛了?
session A 先啓動,這時候會對錶 t 加一個 MDL 讀鎖。由於 session B 需要的也是 MDL 讀鎖,因此可以正常執行。之後 session C 會被 blocked,是因爲 session A 的 MDL 讀鎖還沒有釋放,而 session C 需要 MDL 寫鎖,因此只能被阻塞,之後所有要在表 t 上新申請 MDL 讀鎖的請求也會被 session C 阻塞(隊列)。所有對錶的增刪改查操作都需要先申請 MDL 讀鎖,就都被鎖住,等於這個表現在完全不可讀寫了。如果某個表上的查詢語句頻繁,而且客戶端有重試機制,也就是說超時後會再起一個新 session 再請求的話,這個庫的線程很快就會爆滿。
 
*如何安全地給小表加字段?
        首先要解決長事務,事務不提交,就會一直佔着 MDL 鎖。在 MySQL 的 information_schema 庫的 innodb_trx 表中,可以查到當前執行中的事務。如果要做 DDL 變更的表剛好有長事務在執行,要考慮先暫停 DDL,或者 kill 掉這個長事務。
 
但是如果你要變更的表是一個熱點表,雖然數據量不大,但是上面的請求很頻繁,而你不得不加個字段,該怎麼做呢?
       這時候 kill 可能未必管用,因爲新的請求馬上就來了。比較理想的機制是,在 alter table 語句裏面設定等待時間,如果在這個指定的等待時間裏面能夠拿到 MDL 寫鎖最好,拿不到也不要阻塞後面的業務語句,先放棄。之後開發人員或者 DBA 再通過重試命令重複這個過程。MariaDB 已經合併了 AliSQL 的這個功能,所以這兩個開源分支目前都支持 DDL NOWAIT/WAIT n 這個語法
ALTER TABLE tbl_name NOWAIT add column ...
ALTER TABLE tbl_name WAIT N add column ...

 

3、總結

*全局鎖主要用在邏輯備份過程中。對於全部是 InnoDB 引擎的庫,建議你選擇使用–single-transaction 參數,對應用會更友好。
 
*表鎖一般是在數據庫引擎不支持行鎖的時候纔會被用到的。如果你發現你的應用程序裏有 lock tables 這樣的語句,比較可能的情況是:
  • 系統現在還在用 MyISAM 這類不支持事務的引擎,要安排升級換引擎
  • 引擎升級了,但是代碼還沒升級。解決方法:把 lock tables 和 unlock tables 改成 begin 和 commit
 
*MDL 會直到事務提交才釋放,在做表結構變更的時候,一定要小心不要導致鎖住線上查詢和更新。
 
*問題:備份一般都會在備庫上執行,你在用–single-transaction 方法做邏輯備份的過程中,如果主庫上的一個小表做了一個 DDL,比如給一個表上加了一列。這時候,從備庫上會看到什麼現象呢?
答:備份關鍵語句如下:
Q1:SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
Q2:START TRANSACTION  WITH CONSISTENT SNAPSHOT;
/* other tables */
Q3:SAVEPOINT sp;
/* 時刻 1 */
Q4:show create table `t1`;
/* 時刻 2 */
Q5:SELECT * FROM `t1`;
/* 時刻 3 */
Q6:ROLLBACK TO SAVEPOINT sp;
/* 時刻 4 */
/* other tables */

 

  • 在備份開始的時候,爲了確保 RR(可重複讀)隔離級別,再設置一次 RR 隔離級別 (Q1);
  • 啓動事務,這裏用 WITH CONSISTENT SNAPSHOT 確保這個語句執行完就可以得到一個一致性視圖(Q2);
  • 設置一個保存點,這個很重要(Q3);
  • show create 是爲了拿到表結構 (Q4),然後正式導數據 (Q5),回滾到 SAVEPOINT sp,在這裏的作用是釋放 t1 的 MDL 鎖 (Q6)。
DDL 從主庫傳過來的時間按照效果不同,我打了四個時刻。題目設定爲小表,我們假定到達後,如果開始執行,則很快能夠執行完成。
參考答案:
  • 如果在 Q4 語句執行之前到達,現象:沒有影響,備份拿到的是 DDL 後的表結構
  • 如果在“時刻 2”到達,則表結構被改過,Q5 執行的時候,報 Table definition has changed, please retry transaction,現象:mysqldump 終止
  • 如果在“時刻 2”和“時刻 3”之間到達,mysqldump 佔着 t1 的 MDL 讀鎖,binlog 被阻塞,現象:主從延遲,直到 Q6 執行完成
  • 從“時刻 4”開始,mysqldump 釋放了 MDL 讀鎖,現象:沒有影響,備份拿到的是 DDL 前的表結構
 
*Online DDL:
  • 拿MDL寫鎖
  • 降級成MDL讀鎖
  • 真正做DDL
  • 升級成MDL寫鎖
  • 釋放MDL鎖
1、2、4、5如果沒有鎖衝突,執行時間非常短。第3步佔用了DDL絕大部分時間,這期間這個表可以正常讀寫數據,是因此稱爲“online ”
 
 
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章