首頁 > 軟體

MySQL的鎖機制之全域性鎖和表鎖的實現

2023-01-16 14:03:07

前言

對mysql鎖的總結學習,本文將圍繞,加鎖的概念,加鎖的應用場景和優化,以及不加鎖會導致的問題這些方向進行總結學習。mysql的全域性鎖和表鎖是本文的重點

一、全域性鎖

全域性鎖的介紹以及使用

全域性鎖就是對整個資料庫範例進行加鎖。
MySQL提供了一個加全域性讀鎖的方法,如下:
全域性讀鎖定:

FLUSH TABLES WITH READ LOCK ;

執行了命令之後所有庫所有表都被鎖定唯讀,解鎖:

UNLOCK TABLES ;

加了全域性讀鎖之後,整個資料庫都處於唯讀的狀態,當其他執行緒有資料更新語句(資料的增刪改)、資料定義語句(包括建表、修改表結構等)和更新事務的提交語句都將會被阻塞。

全域性鎖的應用場景

瞭解了全域性鎖的基本概念後,心想全域性鎖的鎖粒度這麼大,效率肯定特別低,應該很少使用吧。但是開發者設計出來這個,肯定有它的使用場景啊。接下來我們看下:
通過學習瞭解到,全域性鎖最經典最常用的場景是用做全庫邏輯備份 (就是把整個庫的每個表select出來存放成文字)
把整個資料庫都鎖住,不能有更新操作,想想都危險,鎖的粒度太大了。
比如:
如果在使用者存取高峰期,這期間需要資料庫的有關表進行更新操作。這是如果你備份整個庫是在主庫上備份,那麼備份期間都不能執行更新操作,這是業務基本上就得停擺,影響使用者體驗。而如果你選擇在從庫上備份,那麼備份期間從庫不能執行主庫同步過來的binlog,會導致主從延遲。

此時,你肯定想那加鎖這麼麻煩危險,那還不如不加鎖呢。如果不加鎖會導致什麼後果呢

不加鎖導致的危害

這裡我舉個簡單的購物例子,你就瞬間清楚了。
比如,購物。我在某網站上買了一件商品,同時維護這網站的也準備發起一個資料庫的邏輯備份。
如果時間順序上是先備份我們使用者的賬號餘額表,然後使用者購買,然後備份使用者商品表。
此時,會出現什麼問題呢。結果會發現,我們使用者的賬號餘額沒減少,但是多了件購買的商品。這也是不是感覺挺好,我們使用者賺大了啊。不過如果反過來呢,那我們豈不是虧大了。

反觀,會出現這麼一個現象的原因是什麼,其實就是備份的庫,備份的庫中的表不是一個邏輯時間點的,即前後沒有一致性。

提到一致性,我們會想到那不直接在可重讀隔離級別下開啟一個事務,進行資料邏輯備份不就好了嗎,那豈不是比加鎖好。

想法挺好的,但是也需要系統支援啊。比如像MyISAM這種不支援事務的引擎,只能通過FTWRL方法了。

加鎖和其他方法對比

通過上面可以知道,整個庫進行備份,就是先把整個庫通過加全域性讀鎖,把整個庫設定成唯讀狀態。
此時,你肯定會想,把整個庫設定成唯讀狀態,我還知道使用如下命令進行設定啊,不必要加鎖啊

set global readonly=true;

確實,readonly這種方式也可以讓全庫進入唯讀狀態。但是對於一些特殊情況,如在例外處理機制上,如果執行FTWRL命令之後,使用者端發生異常斷開,那麼MySQL會自動釋放這個全域性鎖,整個庫回到可以正常更新的狀態,而將這個庫設定為readonly之後,如果使用者端發生異常,則資料庫就會一直保持readonly狀態,這樣會導致整個庫長時間處於不可寫狀態;所以還是建議使用全域性鎖比較合適

瞭解完了全域性鎖,接下來我們再來學習以下表鎖

二、表鎖

表鎖的介紹以及使用

MySQL裡面表級別的鎖有兩種:一種是表鎖,一種是後設資料鎖(meta data lock,MDL)。

對於表鎖,加鎖的語句是:

LOCK TABLES tbl_name ; #不影響其他表的寫操作

解鎖的語句是:

UNLOCK TABLES ;

另一類表級的鎖是 MDL(metadata lock) 。MDL不需要顯式使用,在存取一個表的時候會被自動加上。

表鎖的應用場景

針對表鎖,在MySQL發展初級階段,沒有設計出更細粒度的鎖時,表鎖經常被用於處理並行(InnoDB支援行鎖所以一般不會使用表鎖)。舉個例子,如果在某個執行緒A中執行lock tables t1 read, t2 write;這個語句,則其他執行緒寫t1、讀寫t2的語句都會被阻塞。同時,執行緒A在執行unlock table之前,也只能執行讀t1、讀寫t2的操作。連寫t1都不允許,自然也不能存取其他表。

對於MDL鎖,是存取一個表的時候自動加上的。為什麼呢,這也是因為時間邏輯點的不同。比如,如果一個查詢正在遍歷一個表中的資料,而在此執行期間另一個執行緒對這個表結構做變更,刪了一列,那麼查詢執行緒拿到的結果跟表結構對不上,就會出錯。為此,為了解決這些問題。當對一個表做增刪改查操作的時候,加MDL讀鎖;要對錶做結構變更操作的時候,加MDL寫鎖。

為此需要注意的還有一點,並不是系統預設為每一存取表的操作自動新增了MDL鎖就會萬事大吉。比如,對於一個表t執行如下的操作:

session A先啟動,對錶t加一個MDL讀鎖
因為session B需要的也是讀鎖,可以正常執行
因為session A的MDL讀鎖還沒有釋放,而session C需要MDL寫鎖,因此會被阻塞
session C之後的所有要對錶申請的讀鎖頁會被session C阻塞,最終導致這個表完全不可讀寫
此時如果對這個表的查詢比較頻繁,並且使用者端也有重試機制,那麼這個庫的執行緒很快就會爆滿

造成這個問題的原因是,事務中的MDL鎖,在執行語句的時候開始申請,但是語句結束後並不會馬上釋放,而是等到整個事務提交後再釋放。

因此,瞭解了這個問題,以及這個問題出現的原因。
那麼我們如何解決安全的給表加欄位呢

首先要解決長事務,畢竟事務不提交,就會一直佔著MDL鎖。。因此如果要做DDL變更表的時候,查到剛好有長事務在執行,可以考慮暫停DDL操作或者kill掉長事務
如果要操作的表是個熱點表,請求比較頻繁,此時如果採用kill,那麼新的請求立馬就來了,未必管用。因此比較合適的操作是:在alter table語句裡面設定等待時間,如果在這個指定的等待時間裡面能夠拿到MDL寫鎖最好,拿不到也不要阻塞後面的業務語句,先放棄。之後再通過重試命令重複這個過程。

到此這篇關於MySQL的鎖機制之全域性鎖和表鎖的實現的文章就介紹到這了,更多相關MySQL 全域性鎖和表鎖內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com