做個有深度的DBA:MySQL鎖機制實踐

本文轉自高效運維

姜宇祥,攜程資深數據庫工程師


這麼熱天能來的都是真愛,我給大家講一下這個課題,主要講源碼,這個課題與運維看起來有點小差別。

你能看源碼,至少你在運維時候可以跟開發說是看源碼怎麼怎麼樣,我們就可以更有底氣。

而且有點很重要,我們看數據庫裡面,現在大家很多用的,不管開源的MySQL,還是閉源的像 oracle 都是國外數據庫,我們數據庫底子還是挺薄,越來越人開始研究源碼和寫源碼,這樣我們數據庫才會蓬勃發展,跟我們的國策,提倡國產這個東西,我覺得大家都要承擔起學習源碼、研究源碼的美好願望和美好的意志。

今天我給大家帶來一個入門的MySQL鎖機制與實踐,主要裡面給大家介紹一下它的源碼入口之類東西,幫助大家以後這方面讀源碼讀得更順暢。

做個有深度的DBA:MySQL鎖機制實踐


首先我先介紹一下MySQL兩層架構,MySQL的兩層架構所有數據庫裡面,除了MySQL以外,其他數據庫都沒有這個特點,MySQL兩層架構就是它把服務層和存儲引擎層拆成兩個層次來看待,服務層集中所有通用化的功能,如網絡通訊和語法分析等工作。

存儲層它做到的是進行數據相關的存儲,比如它是採用內存方式的存儲,比如像之前的賴老師介紹的memcache,它是內存存儲,還有文件方式存儲,像InnoDB。

它的好處是什麼,讓這個數據庫非常靈活,但是同樣MySQL這兩層存儲也提出它的問題缺點,由於它是一個關係性數據庫,也涉及到事務,再處理事務時候,我們使用多引擎時候一定要注意這個問題。

做個有深度的DBA:MySQL鎖機制實踐


我們往下看一下,這是非常經典的MySQL一個架構,它在這裡面列出來,從客戶端我用的是什麼,它發出的消息或者說是請求,到MySQL server,它的語法分析或者優化器部分,還有這些東西,這都是MySQL在很多上面。如果我們刨去底層是通用部分,它把這部分集中起來,這部分它做得非常好。下面可以接不同存儲引擎,像比如 InnoDB 和 MEMORY,它不支持事務,如果兩個共用大家要小心。

BLACKHOLE 是挺有意思引擎,大家以後做這個可以引進進來,它不實際插數據,我們把這個作為一箇中繼,binlog存儲本地再傳輸到下一個節點,這是體現MySQL非常靈活性的方面。

再說這兩種鎖,MDL鎖在服務器層,我們建表這些事情,比如表和數據庫元數據信息,它都是由server保護,這些訪問需要用MDL鎖。InnoDB是存儲引擎,它實現數據庫裡面最標準隔離級,這部分又自己實現一個鎖,InnoDB實現這個鎖,把數據併發操作這些事情在這一層做掉。

做個有深度的DBA:MySQL鎖機制實踐


我們看一下元數據鎖和InnoDB鎖包含什麼東西,元數據鎖像我剛才說,它鎖的是數據庫對象。

所謂數據庫對象包括什麼,首先它有一個global對象,這個對象大家並不常接觸,實際我們維過程中最容易碰到的 globle read lock,實際這是global鎖存在原因。

還有數據庫對象、表對象、存儲過程對象、函數對象,還有觸發器對象,這都是它服務器層所涵蓋的概念,元數據鎖對應這些的鎖,它保護我們存儲每一行數據,下面是表鎖和行鎖,對於InnoDB來說表鎖非常少見,很少碰到,這裡面我們主要介紹InnoDB的一個間隙所實現原理。

我們來看一下,介紹一下MySQL的元數據鎖信息,我會從這幾個方面講元數據鎖。第一元數據鎖之間有關係,元數據鎖的類型,元數據鎖每一個對象針對的元數據鎖有一些從屬關係,還有這些元數據鎖什麼時間請求,什麼時間釋放,我們怎麼讀它,我們還要了解怎麼讀元數據鎖的源碼,大家得源碼比較感興趣是入口點,還有結合一個案例去分析,我們去教大家怎麼讀MySQL的源碼。

下面列出MySQL所有的元數據鎖類型,global我們之前提到過,應該怎麼講呢,它在上面做了最高層次做了就是global鎖,就是實例級進行上鎖。

做個有深度的DBA:MySQL鎖機制實踐


它下面是TABLESPACE,TABLESPACE對於MySQL的這個參數設置來講,實際上針對每個表的表鎖,它下面還有一個 table 表鎖,實際上可以理解成TABLESPACE 物理方面的鎖,table 鎖針對表對象的鎖。

還有一個schema,schema是MySQL裡面有一個概念上的混淆,這裡面元數據鎖對應schema的對象,還有我們下面FUNCTION鎖、PROCEDURE鎖、TRIGGER鎖,這三個鎖,commit鎖,我在commit提交的時候,它需要持有讓COMMIT串行化方式的鎖,並不常見到,但是每次commit一定都會有,它和global差不多的。

還有USER LEVEL LOOK,這是MySQL向外提供,給你使用MySQL的時候,可以使用對外提供的鎖,元數據鎖,你可以在兩個不同之間,你使用這個鎖的話,可以做到這個之間的同步。

還有locking service,實際上它是更進一步USER_LEVEL_LOCK,前面的USER_LEVEL_LOCK是有兩個函數跟它相關,這個函數可以去查MySQL的手冊,它明確提供的接口,它可以把兩個之間,對同一個對象進行上鎖,它可以對兩個之間同步。

做個有深度的DBA:MySQL鎖機制實踐


類型有這麼多數據鎖,這張圖可以看到數據鎖之間的看到。global鎖下面,它所謂的關係是什麼?這裡面關係是在大部分情況下,如果我要對這裡面某一個元數據上鎖的時候,它有一些前置鎖。

在上這個鎖之前,我把其他對象的鎖上了,我把它稱之為關係。看這個關係箭頭所指,它的關係從屬。

比如我做tablespace上鎖時候加global的鎖,這樣它通過看圖,我們知道一旦做底層操作時候需要上很多,依照這個線它要上很多鎖,我們在做下面操作時候可能會產生更多的衝突。也不叫產生更多的衝突,要hold更多的鎖。

做個有深度的DBA:MySQL鎖機制實踐


再講元數據,鎖這個東西有申請和釋放過程,元數據鎖的申請,它有這樣幾種,它鎖申請的時候在你使用的時候,它才進行申請。

但是如果它在釋放時候,它會一定遵照這三個類型釋放,而這三個類型指明瞭程序運營某一個點時候,固定點的時候針對這些鎖進行釋放。比如這裡面的statement鎖,當我們做一組SQL操作時候加上這些。

它在元數據上鎖的時候,對元數據操作進行上鎖,如果指定STATEMENT的時候,它在STATEMENT結束時候會進行判斷,看到有這些上了STATEMENT鎖的時候會在這時候進行釋放。

看下面這一段比較清楚,先介紹有三種類型:STATEMENT級別、TRANSACTION級別、EXPLICIT,就是明確指明瞭。

做個有深度的DBA:MySQL鎖機制實踐


看這個流程大家可能會清楚一點,在一開始我說的你執行時候獲取這些鎖。像之前某一個元數據鎖制定MDL STATEMENT鎖,剛才我說一批SQL過來,設置成這些信息的鎖全部被釋放掉。

舉個例子,操作這張表先設置一個MDL鎖,當這個執行完之後把這個鎖放掉,如果不放掉也就很簡單,別人再進行一定會被鎖掉。STATEMENT的鎖,還有TRANS鎖,一個事情批量執行完之後,所有MDL的鎖被釋放。一個事務全部執行完之後,所有的鎖基本上全部會釋放掉。

我們再講一下,MDL鎖,它的信息就很多,我們在看源碼的時候經常被自己混亂掉。你知道了其實就是那麼一點東西,你不知道的時候就會四處撒網去看,我們剛才介紹源數據鎖核心介紹的關係,它的類型,還有釋放點,這幾個地方最重要,看到這幾個最重要東西,我們看源碼的時候可以看到劃紅線的部分,劃紅線部分MDL核心源碼,我們研究MDL如何操作運行的時候,我們深入MDL.h和MDL.cc,定義之前我們提到的這些類型的宏(這裡假設大家有C方面經驗,對宏登概念有鎖瞭解)。

做個有深度的DBA:MySQL鎖機制實踐


通過這些宏我們找對應的信息,紅框對應的文件,操作上鎖、解鎖,函數這裡面定義。使用這些函數是下面的這些函數,是在這裡面使用的。

核心,在這裡面看代碼就是這個東西,看哪幾個函數最重要。通常來講看代碼的時候有幾個函數最重要,就是越底層的函數是越重要,大家最後入口,以這個例子來講,不管是哪個,最終都要落到函數里面來,都在這裡面描述,不管是哪一種鎖最終都是通過這個函數實現了上鎖的東西。

所以你們要是在調試MySQL的時候,想調MDL的相關代碼設置斷點一定在這個函數里面設置斷點。這個函數作為最底層函數,不過下面並不是完全,我只是舉幾個例子。

比如lock schema,做這個操作,最終它會通過這些落到這個函數里面,所以當我們想要看這個操作如何操作,這塊設一個斷點,可以通過這個堆棧瞭解一系列調用什麼樣。

做個有深度的DBA:MySQL鎖機制實踐


這個就是講元數據鎖與PFS,元數據在上鎖時候,我在進行等待,我等待了一個什麼樣的鎖,就是這個東西。當我們在show processlist或者performance_schema下面,然後metadata_locks表查的時候,我們讀到就是這些信息,實際這些信息和我們這個鎖是一一對應的。

我們講一個從線上碰到的問題,到如何去看源碼找到根源,這個作為一個例子,幫助大家加強對於MDL源碼概念。

做個有深度的DBA:MySQL鎖機制實踐


這是我們線上碰到現象,重新做備份160個左右連接被阻塞,情況就是這個圖。這裡面有一個time時間,處理這個問題的時候直接到time時間最長連接,整個問題就都解決了,我們問題解決了還要找它的根本原因,瞭解到這個原因之後,以後可以避免類似情況再發生。

我們在讀這個東西的時候,有一個最可疑的,最長的這個時間是,不好意思,沒在這裡面,當時拿了一個大表的非常長操作,這個操作理論上講不應該鎖那麼多數據。

在這裡面拿出來的這段,只有這一個,當時做一個維護操作,只有這個東西才會把整個全局進行鎖掉,針對這個東西我們查為什麼前面這個查詢導致後面出現了全局的鎖,當時分析的一些結果。

我們當時調了,因為我們已經知道是flush tables with read lock造成其他事務阻塞,我們看它在這個裡面做了什麼操作,之前我們看到的這個信息提示很重要,Waiting for global read lock元數據鎖導致問題。

做個有深度的DBA:MySQL鎖機制實踐


我們在這塊設置斷點,我們執行flush tables with read lock得到一個堆棧電梯,在調試的時候我們強調堆棧電梯,而且以後你們碰到問題向別人求助的時候,最好也能抓住這樣堆棧電梯。

你們如果看阿里或者騰訊發出來的文章,就是說他們講一些東西的時候,他都會把堆棧電梯給你拿出來看當時是什麼情況。

當時我們看堆棧電梯的時候,它這裡面,我們設斷點在這個函數,然後調用到這個是我們很期待的。再往上就是這個函數,這時候我們知道這個函數里面發生的事情是我們所不希望它產生全局鎖的情況。

做個有深度的DBA:MySQL鎖機制實踐


再看一下這段代碼,通過堆棧找到代碼,這段代碼上面我們看什麼,它在上這個read lock之後,它後面有這個,在上完這個鎖之後,它有做了一個close cached tables,我們知道flush tables這個操作被鎖住的,它在上完這個東西之後,它做了一個close cached tables。

之前我們的長操作,它在一開始一定要起來,它在不執行完不會釋放那些,它在執行時候就這個位置等到另外一個全部執行完,這樣它等在這之後hold全局的read lock,做元數據操作加一個read lock獲取信息,後面全部被鎖掉了。

通過這個方式,這個斷點之後找到全部前因後果,通過這個運維或者開發,所有做超常查詢的時候,只要上面做運維操作都可能把別人卡死,做這個幾千萬行數據,網絡傳輸到本地,這種情況下別人幹掉,會影響到別人。

這個東西給它分析出來之後,以後可以明確告訴開發或者誰,這個東西不能這麼做。這是源碼的東西,理直氣壯。

MDL可以講的就是這麼多的,像我之前說的知道了很簡單,不知道一直摸不著頭緒,前面大家注意的就是這些。

下面講InnoDB鎖,我分這幾個部分介紹一下,實際上InnoDB鎖是一個非常複雜,PPT裡面只能介紹一部分,介紹的這部分也只是針對這種情況下的一個間隙鎖的實現,前面還會介紹一些前置知識,事務隔離級,在講InnoDB會講鎖的級別,間隙鎖怎麼樣,還有和上面MDL告訴源碼主要哪幾個地方,什麼重要入口在哪裡。還有再講一下我在攜程針對這方面做了哪些源碼改造。

做個有深度的DBA:MySQL鎖機制實踐


做個有深度的DBA:MySQL鎖機制實踐


先講第一個前置知識,包括事務隔離級裡面三個相現象,一個髒讀,一個不可重複讀,一個幻讀,還有一個是它的事務隔離級,就是讀未提交、讀提交,還有它是一個什麼樣的狀況。因為我在工作的過程中,很多人實際上對這個都是理解不夠透徹,我個人認為理解不夠透徹,我想把這個東西介紹一下,可能很多人已經很瞭解了,我把它再細化講一講。

這裡面先講髒讀,什麼是髒讀,我拿賴老師舉現場例子,我和他比較熟,幫助大家形象化理解,賴老師做一件事情,寫一點東西,正好我路過看到賴老師寫的東西,我說賴老師你這個東西寫得不錯,我拿這個東西給他傳播了,結果賴老師覺得寫得不滿意,按照數據庫的說法回滾掉,不要了,結果我還傳播出去了,這個就是髒讀,這個讀到別人回滾數據,還使用到它,這就是髒讀。

針對髒讀在讀位隔離級提交才會發生,為了避免髒讀,有讀提交隔離級出現,你只有提交了,剛才賴老師說寫稿,定稿的稿子給我,我看了之後,這是它提交的東西,我再拿出去用就OK,這是讀提交。

但是讀提交也有一個問題,賴老師做第一個東西拿出去給別人用,但是賴老師又修改一次變成第二版本,這時候拿第一個稿,碰到什麼情況?拿第一版本稿子再給別人用的時候,實際上已經有第二個版本情況出現了,這個時候就變成了不可重複讀的這種現象了。

在我的操作過程中,我讀到了兩次,就是碰到了兩次兩個不同的版本,針對這種情況下有一個可重複讀的隔離級,很多數據庫實現時候,裡面有一個MVCC方式,一個事務裡面讀到我事務之前的版本,我這個事務之前最新的一個版本,這樣避免了不可重複讀。

但是可重複讀隔離級下面還有一個問題,帶一個查詢條件,比如賴老師寫了一篇稿子,這個時候我能只操作這一個,只引用了這一個稿子,但是產生了第二個稿子,讀第二次發現賴老師的第二個稿子出現了,變成了一個幻讀的現象,這個幻讀現象是什麼?

我在一次事務裡面查詢結果級不一樣,又引入串行化。看這個事務隔離級可以想前輩怎麼逐漸數據庫演化成現在這個樣子,最早誰也不知道,沒有事務隔離級大家都不知道,都是在那邊寫,慢慢出現這種情況,這也是軟件演化的概念。我推薦一本書講軟件演化,挺有意思的現象。

做個有深度的DBA:MySQL鎖機制實踐


鎖的基本原理我介紹完之後,我們可以進入到InnoDB具體的信息裡面,InnoDB有表鎖、行鎖和間隙鎖,間隙鎖就是行鎖類型,特殊操作,加上特殊標識之後可以認為間隙鎖。

做個有深度的DBA:MySQL鎖機制實踐


它有意向共享鎖、意向排它鎖和共享所、排他鎖、自增鎖,自增鎖是InnoDB自己實現的自增列的鎖,這是全局鎖,排它性是這樣。它是直接列的數組矩陣,兼容性就是這樣子。通常情況下不會碰到IS鎖和X鎖衝突情況。S鎖和X鎖放在表級鎖,IS鎖和IX鎖放在進入級別上面。

我們講InnoDB的間隙鎖原理,間隙鎖主要用在不可充分讀,實際上MySQL的第三個隔離級別,可重複讀的級別部分實現串行化,在一個事務之內保證它的結果級基本上保持一致,投入這個間隙鎖實現的。

做個有深度的DBA:MySQL鎖機制實踐


做個有深度的DBA:MySQL鎖機制實踐


我們先建一個表,插入這四行數據,我們怎麼理解這個間隙鎖。假如前面做一個begin;select * from t_lock where f2=16 for update,在之前的表裡面是沒有數據,它在查詢的過程中,實際上查到大於它查詢條件的最小一條記錄,它先定位這條記錄,定位這條記錄上之後,對這條記錄的行鎖加特殊標識,當其他事務進行插入的時候,因為它插入的時候一定查詢到一個插入的點。

比如我在插入15的時候,一定也會通過前面查詢定位到17這個上面,這時候它在插入時候發現17上面有一個行鎖,而且這個行鎖加了一個特殊標識,就是加了這個標識變成間隙鎖,這時候它會被鎖住。

為什麼也這樣一個間隙鎖,它達到了行級不發生改變的情況。比如再插一個這個語句等於16,不把17鎖定。

有一個人進來插進來16了,這個查詢的結果,第二次查詢的時候能把這個結果級,提交了就能把這個結果級查詢出來,這個就是間隙鎖的作用。

我每次查到一行記錄的時候,在這個情況下每查到一行記錄進行加鎖,這是間隙鎖實現,通過這種方式避免結果級重複查詢的時候出現行級不一致的現象。這個是剛才講的操作,會把東西鎖在裡面的實例。

做個有深度的DBA:MySQL鎖機制實踐


我們再講一下間隙鎖,看一下間隙鎖在內存裡面打印的結果,MySQL鎖結構非常簡單,就是整個lock打印出來就是這麼多,鎖處於哪一個事務,這個鎖在哪一個index上,這是C裡面特殊語法結構,告訴你上一個鎖是table鎖還是lock鎖。這個是加特殊標識,分成16進制是512,這是間隙鎖內存裡面,打印出來間隙鎖的樣例。

看它的核心源碼這幾個函數,大家有興趣一定這幾個漢書入手,locko,lock.h,IS、IX類型,行鎖類型,還是表鎖類型,這些類型都是在這些.h裡面定義。如何上鎖的地方,在trx0trx.cc 、trx0sys、trx0rec.cc,這是MySQL事務實現的一個非常重要的核心代碼,像行鎖的是在這裡面的。

做個有深度的DBA:MySQL鎖機制實踐


我再講一下攜程做了相關的事務鎖的改造,這個是我們最 常見碰到的現象,發生死鎖一定出這個東西,下面的紅框產生死鎖,這個操作產生死鎖的事務,上面是它前面的一個事務,假設我們有一個環,這個操作造成死鎖,一直到後面產生閉環,這是一個死鎖。

做個有深度的DBA:MySQL鎖機制實踐


打印這個信息,這個信息不夠格式化,產生的內容不好讀。第二點,不能把所有參與到死鎖裡面的事務全部打印出來,我們針對這種情況,實際上我們做了改造,我們希望能把這些信息都格式化出,而且可視化展示。我們做了格式化輸出,把它都輸出成這個格式,後面是一個數組,哪些事務全部打印出來。

做個有深度的DBA:MySQL鎖機制實踐


做個有深度的DBA:MySQL鎖機制實踐


這裡面,之前我們看圖像裡面打印一個鎖信息,我們操作的時候把一個事務裡面所有上鎖信息全部打印出來了,可視化的操作時候就是把之前輸出的信息全部拿出來之後,可以看到這樣一個結果,這個對我們線上運維還是挺有幫助。不過有一個問題是什麼,你判斷不出來,這裡面打印信息還是不夠全,不能把所有鎖涉及到的操作,就是產生所的語句打印出來,我們改進時候有一個配套工程,做一個MySQL的全量trx輸出,實際上運維可以根據線上這些信息把當時開發所做的事情拿出來,定位開發的一些問題。

InnoDB的源碼,鎖就講完了,介紹我自己學習源碼的個人經驗,希望這個東西幫助大家學習源碼。

做個有深度的DBA:MySQL鎖機制實踐


首先第一點,你一定要先了解數據庫的絕大部分功能,然後再去研究它的源碼,否則的話,你直接拿過來源碼看通常是一頭霧水。因為一個好的,像MySQL 源碼寫得很不錯,很多函數名和變量的定義能直接跟它的功能關聯。如果你不熟悉功能直接看源碼時候,你通常找不到它的關聯性。

第二個,觀摩他人源碼學習,怎麼觀摩?網上很多人提供了很多功能性的這個文件,你拿到這個知道實現什麼功能,你拿到這個文件知道做了哪些操作,這樣可以反推這個函數做什麼,這個函數怎麼實現,有一個直觀印象,通過某一個功能點嵌入進去,功能到源碼還是有點大海撈針的感覺,到下面一步可以更針對性的研究。

還有最後一步,最後一個,我們學習源碼可以擴充自己對整個數據庫的瞭解,像之前我介紹的MDL時候,實際上我最早的時候在沒有研究這部分源碼的時候,這些東西我都是不知道,通過源碼最終返回加深對功能瞭解,通過這些功能可以幫助我們線上運維做一些其他的事情。

我就講完了,謝謝大家。

說明:本文根據728數據庫沙龍攜程姜宇祥老師的演講整理而成。


分享到:


相關文章: