1. 程式人生 > >InnoDB日誌管理機制(四) – 運維派

InnoDB日誌管理機制(四) – 運維派

本文接著上文,開始講述MTR。

什麼是MTR InnoDB物理事務

上面已經提到了關於MTR的概念,實際上,它是InnoDB儲存引擎中一個很重要的用來保證物理頁面寫入操作完整性及永續性的機制。之所以被稱為MTR,是因為它的意義相當於一個Mini-transaction,用MTR來表示,這裡把它稱作“物理事務”,這樣叫是相對邏輯事務而言的。

對於邏輯事務,熟悉資料庫的人都很清楚,它是資料庫區別於檔案系統最重要的特性之一,它具有ACID四個特性,用來保證資料庫的完整性——要麼都做修改,要麼什麼都不做。物理事務從名字來看,是物理的,因為在InnoDB儲存引擎中,只要是涉及檔案修改、檔案讀取等物理操作的,都離不開這個物理事務,可以說物理事務是Buffer Pool中的記憶體Page與檔案之間的一個橋樑。

通過下面的圖,可以先了解一下MTR在InnoDB中的作用或意義。

MTR

從上圖中可以看出,不管讀還是寫,只要使用到底層Buffer Pool的頁面,都會使用到MTR,它是上面邏輯層與下面物理層的互動視窗,同時也是用來保證下層物理資料正確性、完整性及永續性的機制。

前面已經介紹過InnoDB的頁面Buffer Pool系統,已經知道在訪問一個檔案頁面的時候,系統都會將要訪問的頁面載入到Buffer Pool中,然後才可以訪問這個頁面,此時可以讀取或更新這個頁面。在這個頁面不斷更新變化的過程中,有一個系統一直扮演著很重要的角色,那就是日誌系統。因為InnoDB採用的也是LOGWRITE-AHEAD,所以所有的寫操作,都會有日誌記錄,這樣才能保證資料庫事務的ACID特性。

而寫日誌是一個物理操作,其實它也需要一個完整性。比如在底層頁面插入一條記錄,如果只修改頁頭資訊而沒有修改頁尾資訊,其實對於這個頁面來說是不完整的,所以這個物理操作還是需要一個機制來保證它的完整性的。那麼在InnoDB中,這個機制就是上面介紹的物理事務,因為它也是用來保證完整性的,所以也被稱作“事務”。

物理事務既然被稱為事務,那它同樣有事務的開始與提交,物理事務的開始其實就是對物理事務結構體mtr_struct的初始化,其中包括下面一些成員。

mtr_struct

分別介紹一下每個成員的意義,如下。

  1. memo:
    是一個動態陣列空間,用來儲存所有這個物理事務用到(訪問)的頁面(實際上儲存的就是Buffer Pool中管理頁面的控制塊結構buf_block_t),這些頁面都是被所屬的物理事務上了鎖的(讀鎖或者寫鎖,某些時候會不上鎖)。這個鎖是讀寫鎖、頁面鎖,與邏輯事務中所說的表鎖和行鎖要區分開來。
  2. log:
    也是一個動態陣列空間,用來儲存這個物理事務在訪問修改資料頁面的過程中產生的所有日誌,這個日誌就是資料庫中經常說到的重做(REDO)日誌。
  3. n_log_recs: 表示這個物理事務產生的日誌量,單位為日誌記錄條數。
  4. log_mode:
    表示這個物理事務的日誌模式,包括MTR_LOG_ALL(寫日誌)、MTR_LOG_NONE(不寫日誌)等。
  5. start_lsn: 表示這個物理事務開始前的LSN。
  6. end_lsn: 表示這個物理事務提交後產生的新的LSN。

首先,在系統將一個頁面載入Buffer Pool的時候,需要一個新開始(mtr_start)或者一個已經開始的物理事務,載入時需要指定頁面的獲取方式,比如是用來讀取的還是用來修改的,這樣會影響物理事務對這個頁面的上鎖情況,如果用來修改,則上X鎖,否則上S鎖(當然還可以指定不上鎖)。在確定了獲取方式、頁面的表空間ID及頁面號之後,就可以通過函式buf_page_get來獲取指定頁面了,當找到相應頁面後,物理事務就要對它上指定的鎖,此時需要對這個頁面的上鎖情況進行檢查,一個頁面的上鎖情況是在結構體buf_block_struct中的lock中體現的,此時如果這個頁面還沒有上鎖,這個物理事務就會直接對其上鎖,否則還需要考慮兩個鎖的相容性,只有兩個鎖都是共享鎖(S)的情況下才可以上鎖成功,否則需要等待。當上鎖成功後,物理事務會將這個頁面的記憶體結構儲存到上面提到的memo動態陣列中,然後這個物理事務就可以訪問這個頁面了。

物理事務對頁面的訪問包括兩種操作,一種是讀,另一種是寫。讀就是簡單讀取其指定頁面內偏移及長度的資料;寫則是指定從某一偏移開始寫入指定長度的新資料,同時如果這個物理事務是寫日誌的(MTR_LOG_ALL),此時還需要對剛才的寫操作記下日誌,這裡的日誌就是邏輯事務中提到的REDO日誌。寫下相應的日誌之後,同樣將其儲存到上面的log動態陣列中,同時要將上面結構體中的n_log_recs自增,維護這個物理事務的日誌計數值。

物理事務的讀寫過程主要就是上面介紹的內容,其最重要的是它的提交過程。物理事務的提交是通過mtr_commit來實現的。在講mtr_commit之前,先講一下,什麼時候該提交,內部是如何控制的。

這裡首先需要知道的是,InnoDB的REDO日誌不完全是物理日誌,它包含了部分邏輯意義在裡面,比如插入一行記錄時,MTR記錄的是在一個頁面中寫入這條記錄,內容大致包括頁面號、檔案號(表空間號)及這條記錄的值(包括每個列資訊),這樣就有了邏輯概念。需要注意的是,在做REDO恢復時,需要保證這個頁面是正確的、完整的,不然這個REDO就會失敗,這也正是InnoDB儲存引擎中著名的DOUBLEWRITE存在的意義,不過這是後話。而如果是純物理的REDO,日誌內容應該會拆得更散,比如還是插入一條記錄,它會記錄頁面號、檔案號(表空間號)、頁面內偏移值,並且有多個這樣的REDO記錄,因為會涉及多個位置的修改操作,這就沒有任何邏輯內容了。而針對一個插入操作,需要在一個頁面內的不同位置寫入不同的資料,當然如果是純物理REDO,相應地會產生多條REDO記錄,這是物理與邏輯的簡單區別。

再說MTR的提交,一個邏輯事務是由多個物理事務組成,邏輯事務是用來保證資料庫的ACID特性的,有這個就夠了,所以物理事務可以保證一次物理修改是完整的即可。所謂一次物理修改,可以理解為一個底層的相對完整的寫入操作,比如插入一條記錄的過程中,會包括寫一條回滾記錄及插入時寫入一個頁面等,那麼這些邏輯上是一個動作的物理寫入,就可以被認為是一個獨立的物理事務,也就是在寫回滾記錄時執行mtr_start,寫完之後執行mtr_commit,真正插入時寫一個頁面也是同樣的道理。

mtr_start

(文字太多了,插個圖,不要太火啊!)

接著介紹MTR提交的細節。物理事務的提交主要是將所有這個物理事務產生的日誌寫入到InnoDB的日誌系統的日誌緩衝區中,然後等待srv_master_thread執行緒定時將日誌系統的日誌緩衝區中的日誌資料刷到日誌檔案中,這會涉及日誌刷盤時機的問題,不過還是先來看看MTR、日誌緩衝區及日誌檔案之間的關係,如下圖所示。

MTR

從上圖中可以看出,左邊的若干個MTR產生了各自的REDO LOG,有些MTR已經提交了,有些正在寫入。正在寫入日誌的MTR,它們的日誌都儲存在自己MTR結構的log動態陣列中,這個MTR還是不完整的,所以還是自己儲存著,而對於那些已經提交的MTR,它們對應的日誌已經在提交的時候轉存到了日誌緩衝區中,相當於這些日誌已經是實實在在地產生了,將來必然要佔用資料庫日誌檔案的一部分空間(除非資料庫此時掛了)。

日誌緩衝區的儲存只是一個暫時的中間狀態,日誌緩衝區的大小可以通過引數innodb_log_buffer_size來設定,一般都比較小,儲存不了太多的日誌。因為已經提交併寫入到日誌緩衝的日誌是確定的,所以它們是佔用了LSN的,也就是說它們會使LSN變大。

最後提交的那個MTR代表著整個資料庫最新的LSN值,也就是圖中所示的Log Sequence
number
,這個也正是在MySQL客戶端中執行命令show engine innodb
status\G
時,返回的資訊中Log模組中的第一行,如圖所示。

MTR

而日誌緩衝區也是有大小的,當多個MTR提交時,緩衝區被佔滿了,那麼此時系統會將日誌緩衝區的日誌刷到日誌檔案中(這裡涉及的另一個問題就是日誌刷盤時機,這裡只是一種情況,其他的後面做專門介紹),為其他新的MTR釋放空間。此時,日誌的流向就是從中間的日誌緩衝區向右邊的日誌檔案轉移,上面已經提到過,轉移其實是平移,在緩衝區是什麼內容,寫入檔案也是什麼內容,也是完全連續的,且在日誌檔案中,還是一個個的MTR連續儲存。

最新寫入日誌檔案的那個MTR產生的LSN值(圖中所示的log flushed up
to),其實就是上圖中展示的Log狀態的第二行,也就是日誌最新的寫入檔案的LSN值,這個值的意義很重大,表示的是,到這個LSN為止,所有的修改都是完整的了,如果此時資料庫掛了,寫到這個位置的資料都是可以恢復的,而不需要去關心Buffer頁面是不是被刷到磁碟。但此時在日誌緩衝區中的日誌所對應的操作就丟失了,這裡是否會丟失事務資料與引數innodb_flush_log_at_trx_commit有關係,如果將引數innodb_flush_log_at_trx_commit設定為1,當前事務的提交肯定會將日誌緩衝區中的日誌刷到日誌檔案中;如果設定為2,那麼日誌只是寫入了作業系統快取,並沒有寫入磁碟,那麼此時有可能丟失部分已經提交的事務,丟失多少由作業系統決定,這種情況下,即使資料庫掛了,只要機器不掛,就問題不大,因為作業系統還會將它對應的快取寫入磁碟;但如果設定為0的話,就無能為力了,因為InnoDB只負責將事務對應的日誌寫入到日誌緩衝區中,無論是作業系統,還是資料庫,都不能保證日誌的安全性,所以最好不要設定成這樣。

進一步而言,日誌檔案的大小也是有限的,不可能無限量地將日誌寫入日誌檔案中。前面已經提到過,它是迴圈使用的,如果日誌寫入的頭(圖中所示的log
flushed up to)和尾相遇了,此時日誌就不能再寫入了,因為如果再寫入的話,就要“追尾”了,這樣會將之前產生的日誌覆蓋掉,導致日誌不可用,不完整。此時就會使用一種機制來保證新的日誌還能繼續寫入,尾部日誌還是完整的,這個機制叫做checkpoint(檢查點)。

說白了,日誌產生的作用,是將隨機頁面的寫入變成順序日誌的寫入,從而用一個速度更快的寫入來保證速度較慢的寫入的完整性,從而提高整體資料庫的效能。其根本目的是要將隨機變成順序,所以日誌的量才是一個相對固定迴圈使用的空間。有了這個思想之後,使用檢查點來保證日誌的重複寫入、資料庫完整性就是順其自然的事情。

日誌

(年青俊彥單身中,歡迎留言諮詢)

使用檢查點來保證資料庫完整性的主體思想,主要是讓日誌失效,也就是讓Buffer Pool中的頁面修改寫入到磁碟上面。因為日誌的存在實際上就是讓Buffer
Pool中的Page儘可能少地刷磁碟,儘可能長時間地將頁面資料快取起來,儘可能提高訪問速度,因為不管如何修改,Buffer Pool中的頁面都是最新的,只是不一定寫入磁碟中(沒有刷入沒關係,由日誌來保證)。如果日誌檔案大小不夠用,此時只要將Buffer Pool中的某些頁面刷入到磁碟中,其對應的日誌就失效了,因為這些日誌就是用來保證Page沒有刷入時但資料庫掛了的情況下資料庫的完整性的,而這些Page如果已經寫入磁碟了,相應的日誌也就沒有用了,這就是檢查點的根本意義所在。

而上面提到的,做檢查點時,只是將某些頁面刷入磁碟,其中的”某些”是有講究的。俗話說:“家有三件事,先從緊處來”,現在的問題是日誌空間不夠用了,而日誌是迴圈使用的,必須是按照順序,不能跳著寫,所以最主要的是從LSN值最小的日誌開始,按照從小到大的順序不斷地讓這些日誌失效。每次做檢查點都會有一個比例,此時系統會根據最小的有效LSN(min_valid_lsn)和檢查點處理的日誌比例計算出最大的將要失效的LSN值(取名叫lsn_checkpoint_up_to)。計算完之後,再去掃描Buffer Pool的flush_list連結串列,找出所有被更新過的頁面中,曾經修改這些頁面的MTR對應的LSN中的最小值(因為一個頁面有可能被多次修改,但只需要考慮最小的LSN的那一次,使用的是前面介紹結構buf_block_t時,這裡面所儲存的oldest_modification的值),如果這個值比lsn_checkpoint_up_to值小,就將這個頁面刷入磁碟,也就是說,如果將小於lsn_checkpoint_up_to的MTR修改過的頁面都刷入磁碟了,那麼日誌檔案中在LSN值lsn_checkpoint_up_to以前的日誌就都可以失效了,那麼在整個日誌檔案空間中,從min_valid_lsn到lsn_checkpoint_up_to之間的空間,又可以被重新使用了,直接覆蓋即可,而不會導致資料庫不完整、資料丟失等問題。

上面講的整個檢查點過程,用一個更形象的圖表示如下。

檢查

此時,再接著上面MTR產生日誌的圖來講,上面找到的日誌檔案的位置lsn_checkpoint_up_to就是圖中所示的last checkpoint at,也是上面命令show engine innodb status\G中關於Log部分的第四行資訊。而從這個點開始到最新的已經刷盤的日誌檔案位置Log flushed up to之間的日誌都是有效日誌了,不能被覆蓋,只有空間又不夠用了的情況下,再將最小的有效日誌位置向前推,產生新的位置,像這樣不斷迴圈,周而復始的工作,這就是日誌、Buffer Pool及檢查點之間的工作原理。

上面提到的show engine innodb status\G命令生成的Log部分中顯示的第三行資訊,是Buffer Pool中Page刷盤時刷到的一個最新的LSN。但此時檢查點的最新點不一定做得及時,所以它是大於等於第四行的,而圖中所示的四行對應的值,從上到下以遞減的順序排序,其中的道理都已經非常明確了。

上面已經講過,物理事務和邏輯事務一樣,也是可以保證資料庫操作的完整性的。一般說來,一個操作必須要在一個物理事務中完成,也就是說要麼這個操作已經完成,要麼什麼也沒有做,否則就有可能造成資料不完整的問題,因為在資料庫系統做REDO操作時是以一個物理事務為單位做的,如果一個物理事務的日誌是不完整的,則它對應的所有日誌都不會重做。那麼,如何辨別一個物理事務是否完整呢?這個問題是在物理事務提交時用了一個很巧妙的方法來保證的。在提交前,如果發現這個物理事務有日誌,則在日誌最後再寫一些特殊的日誌,這些特殊的日誌就是一個物理事務結束的標誌,提交時一起將這些特殊的日誌寫入,在重做時如果當前這一批日誌資訊最後面存在這個標誌,則說明這些日誌是完整的,否則就是不完整的,就不會重做。

物理事務提交時還有一項很重要的工作就是處理上面結構體中動態陣列memo中的內容,現在已經知道這個陣列中儲存的是這個物理事務訪問過的所有頁面,並且都已經上了鎖。在它提交時,如果發現這些頁面中已經有被修改過的,這些頁面就成了髒頁,這些髒頁需要被加入到InnoDB Buffer Pool中的更新連結串列中(講BUFFER時已經講過);當然,如果已經在更新鏈中,則直接跳過(不能重複加入),svr_master_thread執行緒會定時檢查這個連結串列,將一定數目的髒頁刷到磁碟中,加入之後還需要將這個頁面上的鎖釋放掉,表示這個頁面已經處理完成;如果頁面沒有被修改,或者只是用來讀取資料的,則只需要直接將其共享鎖(S鎖)釋放掉即可。

上面的內容就是物理事務的一個完整的講述,它是比較底層的一個模組,牽扯的東西比較多,這裡重點講述了物理事務的意義、操作原理、與BUFFER系統的關聯、日誌的產生等內容。

本文就此結束,接下來繼續連載,講述有關日誌的其他內容。

文章來自微信公眾號:DBAce