MySQL 全局鎖和表鎖

根據加鎖的範圍,MySQL 裏面的鎖大體能夠分紅全局鎖,表級鎖,行鎖。 
行鎖已經在前面幾篇文章說過
mysql

1. 全局鎖

全局鎖就是對整個數據庫實例加鎖。MySQL 提供了一個加全局讀鎖的方法,命令是Flush tables with read lock (FTWRL) sql

當你須要讓整個庫處於只讀狀態的時候,可使用這個命令,以後其餘線程的如下語句會被阻塞:數據更新語句(數據的增刪改)、數據定義語句(包括建表、修改表結構等)和更新類事務的提交語句。數據庫

1.1 全局鎖使用場景

全局鎖的典型使用場景是,作全庫邏輯備份(mysqldump)。從新作主從時候 
也就是把整庫每一個表都 select 出來存成文本。 
之前有一種作法,是經過 FTWRL 確保不會有其餘線程對數據庫作更新,而後對整個庫作備份。注意,在備份過程當中整個庫徹底處於只讀狀態。
安全

數據庫只讀狀態的危險性:session

  • 若是你在主庫上備份,那麼在備份期間都不能執行更新,業務基本上就能中止。
  • 若是你在從庫上備份,那麼備份期間從庫不能執行主庫同步過來的binlog,會致使主從延遲。

注:上面邏輯備份,是不加--single-transaction參數多線程

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

1.2 不加鎖產生的問題

好比手機卡,購買套餐信息工具

這裏分爲兩張表 u_acount (用於餘額表),u_pricing (資費套餐表)
步驟:
1 . u_account 表中數據 用戶A 餘額:300
    u_pricing 表中數據 用戶A 套餐:空

2. 發起備份,備份過程當中先備份u_account表,備份完了這個表,這個時候u_account 用戶餘額是300 
3. 這個時候套用戶購買了一個資費套餐100,餐購買完成,寫入到u_print套餐表購買成功,備份期間的數據。
4. 備份完成

能夠看到備份的結果是,u_account 表中的數據沒有變, u_pricing 表中的數據 已近購買了資費套餐100.spa

哪這時候用這個備份文件來恢復數據的話,用戶A 賺了100 ,用戶是否是很舒服啊。可是你的想一想公司利益啊。  線程

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

1.3 爲何須要全局讀鎖(FTWRL)

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

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

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

1.4 全局鎖兩種方法

一. FLUSH TABLES WRITE READ LOCK 
二. set global readonly=true

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

一是,在有些系統中,readonly 的值會被用來作其餘邏輯,好比用來判斷一個庫是主庫仍是備庫。所以,修改 global 變量的方式影響面更大,我不建議你使用。 

二是,在異常處理機制上有差別。若是執行FTWRL 命令以後因爲客戶端發生異常斷開,那麼 MySQL 會自動釋放這個全局鎖,整個庫回到能夠正常更新的狀態。而將整個庫設置爲 readonly 以後,若是客戶端發生異常,則數據庫就會一直保持 readonly 狀態,這樣會致使整個庫長時間處於不可寫狀態,風險較高。

三是,readonly 對super用戶權限無效

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

即便沒有被全局鎖住,加字段也不是就能一路順風的,還有表級鎖了

2. 表級鎖

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

2.1 表鎖

lock tables 表名 read; #該表能夠讀,不能ddl 和 dml 中增刪改,只能讀取表數據 
lock tables 表名 read; # 既不能讀,也不能寫 

 

 

表鎖的語法是 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 命令來控制併發,畢竟鎖住整個表的影響面仍是太大

 

2.2 MDL 鎖

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

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

  • 讀鎖之間不互斥,所以你能夠有多個線程同時對一張表增刪改查。

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

雖然 MDL 鎖是系統默認會加的,但倒是你不能忽略的一個機制。 
好比下面這個例子,我常常看到有人掉到這個坑裏:給一個小表加個字段,致使整個庫掛了。 
確定知道,給一個表加字段,或者修改字段,或者加索引,須要掃描全表的數據。在對大表操做的時候,你確定會特別當心,以避免對線上服務形成影響。而實際上,即便是小表,操做不慎也會出問題。咱們來看一下下面的操做序列,假設表 t 是一個小表。

 

注: 表t 是 innodb 表,mysql版本是5.7.24 自動提交開啓
1. sessionA:
begin;
select * from t limit 1;

2. sessionB:
select * from t limit 1;

3. sessionC:
alter table t add f int;
#會mdl鎖住

4. sessionD:
select * from t limit 1;

  

show full processlist 查看mdl 鎖詳情

咱們能夠看到 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鎖沒有超時時間的限制,只要事務沒有提交就會一直鎖注。

2.2.1 怎麼解決了這個MDL鎖

上面不是說了嗎, 提交或者回滾這個事務。因此要找到這個事務 

怎麼找到這個事務, 經過information_schema.innodb_trx 查看事務的執行時間

 

# 查看事務超過60s的事務
mysql> select * from information_schema.innodb_trx where TIME_TO_SEC(timediff(now(),trx_started))>60\G;
trx_started 表示何時執行的這個事務

#查看系統當前時間
mysql> select now();

事務開始時間和系統如今時間,一看事務執行了這麼久。

查看這個線程id

怎麼處理了這個長事務的線程id了 

首先看show full processlist; 中host 哪一個字段 ,究竟是誰鏈接了數據庫。例:我上面是localhost環境,進去commit或者/rollback ,哪若是不是localhost 環境了,是程序鏈接了這時候就要kill掉了

2.2.2 我身上發生的趣事

上次有個DBA 問我, 形成很大主從延遲,說要怎麼解決。 
我說你怎麼解決的延遲, 知道主從延遲形成的具體的緣由嗎? 
ta給我說,開啓了多線程, 可是延遲仍是很大,基本沒怎麼用到多線程。 
我說你怎麼知道主從延遲,須要開啓多線程複製來解決, ta給我說,網上別人blog這莫說的,我一口老血吐出來。

後來又問ta, 主從延遲正常狀況下,作了什麼操做, ta給我說修改了alter 表結構。 
而後讓ta 看是否是mdl 鎖形成的,讓ta show full processlist,一看果真是mdl 鎖緣由。 
而後跟ta 說 找長事務, 找到之後跟開發商議,這個長事務在作什麼操做可不能夠kill掉。

注:這是我真實遇到,別人問我這樣問題。首先你要知道形成這個結果,你事先作了什麼操做, 而後解決這個問題,最根本你仍是要知道緣由,而後下次避免。 
還有網上的環境,系統版本,應用版本,遇到問題的狀況,跟你是否是同樣,有時候不要盲目相信。

2.3 如何安全地給小表加字段?

首先咱們要解決長事務,事務不提交,就會一直佔着 MDL 鎖。在 MySQL 的information_schema 庫的 innodb_trx 表中,你能夠查到當前執行中的事務。若是你要作 DDL 變動的表恰好有長事務在執行,要考慮先暫停 DDL,或者 kill 掉這個長事務。這也是爲何須要在低峯期作ddl 變動,固然也要考慮具體作什麼ddl,參考官方的online ddl。

2.4 online ddl 過程

  1. 拿MDL寫鎖

  2. 降級成MDL讀鎖

  3. 真正作DDL

  4. 升級成MDL寫鎖

  5. 釋放MDL鎖

一、二、四、5若是沒有鎖衝突,執行時間很是短。第3步佔用了DDL絕大部分時間,這期間這個個表能夠正常讀寫數據,是所以稱爲」online」

相關文章
相關標籤/搜索