前言:整理歸納,僅供個人溫習之用,請支持正版極客時間
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 這樣的語句,比較可能的情況是:
*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 ”