《《MySQL運維內(nèi)參》節(jié)選》要點:
本文介紹了《MySQL運維內(nèi)參》節(jié)選,希望對您有用。如果有疑問,可以聯(lián)系我們。
本文接著上文,開始講述MTR.
上面已經(jīng)提到了關(guān)于MTR的概念,實際上,它是InnoDB存儲引擎中一個很重要的用來保證物理頁面寫入操作完整性及持久性的機制.之所以被稱為MTR,是因為它的意義相當于一個Mini-transaction,用MTR來表示,這里把它稱作“物理事務”,這樣叫是相對邏輯事務而言的.
對于邏輯事務,熟悉數(shù)據(jù)庫的人都很清楚,它是數(shù)據(jù)庫區(qū)別于文件系統(tǒng)最重要的特性之一,它具有ACID四個特性,用來保證數(shù)據(jù)庫的完整性——要么都做修改,要么什么都不做.物理事務從名字來看,是物理的,因為在InnoDB存儲引擎中,只要是涉及文件修改、文件讀取等物理操作的,都離不開這個物理事務,可以說物理事務是Buffer Pool中的內(nèi)存Page與文件之間的一個橋梁.
通過下面的圖,可以先了解一下MTR在InnoDB中的作用或意義.
從上圖中可以看出,不管讀還是寫,只要使用到底層Buffer Pool的頁面,都會使用到MTR,它是上面邏輯層與下面物理層的交互窗口,同時也是用來保證下層物理數(shù)據(jù)正確性、完整性及持久性的機制.
前面已經(jīng)介紹過InnoDB的頁面Buffer Pool系統(tǒng),已經(jīng)知道在訪問一個文件頁面的時候,系統(tǒng)都會將要訪問的頁面載入到Buffer Pool中,然后才可以訪問這個頁面,此時可以讀取或更新這個頁面.在這個頁面不斷更新變化的過程中,有一個系統(tǒng)一直扮演著很重要的角色,那就是日志系統(tǒng).因為InnoDB采用的也是LOGWRITE-AHEAD,所以所有的寫操作,都會有日志記錄,這樣才能保證數(shù)據(jù)庫事務的ACID特性.
而寫日志是一個物理操作,其實它也需要一個完整性.比如在底層頁面插入一條記錄,如果只修改頁頭信息而沒有修改頁尾信息,其實對于這個頁面來說是不完整的,所以這個物理操作還是需要一個機制來保證它的完整性的.那么在InnoDB中,這個機制就是上面介紹的物理事務,因為它也是用來保證完整性的,所以也被稱作“事務”.
物理事務既然被稱為事務,那它同樣有事務的開始與提交,物理事務的開始其實就是對物理事務結(jié)構(gòu)體mtr_struct的初始化,其中包括下面一些成員.
分別介紹一下每個成員的意義,如下.
首先,在系統(tǒng)將一個頁面載入Buffer Pool的時候,需要一個新開始(mtr_start)或者一個已經(jīng)開始的物理事務,載入時需要指定頁面的獲取方式,比如是用來讀取的還是用來修改的,這樣會影響物理事務對這個頁面的上鎖情況,如果用來修改,則上X鎖,否則上S鎖(當然還可以指定不上鎖).在確定了獲取方式、頁面的表空間ID及頁面號之后,就可以通過函數(shù)buf_page_get來獲取指定頁面了,當找到相應頁面后,物理事務就要對它上指定的鎖,此時需要對這個頁面的上鎖情況進行檢查,一個頁面的上鎖情況是在結(jié)構(gòu)體buf_block_struct中的lock中體現(xiàn)的,此時如果這個頁面還沒有上鎖,這個物理事務就會直接對其上鎖,否則還需要考慮兩個鎖的兼容性,只有兩個鎖都是共享鎖(S)的情況下才可以上鎖成功,否則需要等待.當上鎖成功后,物理事務會將這個頁面的內(nèi)存結(jié)構(gòu)存儲到上面提到的memo動態(tài)數(shù)組中,然后這個物理事務就可以訪問這個頁面了.
物理事務對頁面的訪問包括兩種操作,一種是讀,另一種是寫.讀就是簡單讀取其指定頁面內(nèi)偏移及長度的數(shù)據(jù);寫則是指定從某一偏移開始寫入指定長度的新數(shù)據(jù),同時如果這個物理事務是寫日志的(MTR_LOG_ALL),此時還需要對剛才的寫操作記下日志,這里的日志就是邏輯事務中提到的REDO日志.寫下相應的日志之后,同樣將其存儲到上面的log動態(tài)數(shù)組中,同時要將上面結(jié)構(gòu)體中的n_log_recs自增,維護這個物理事務的日志計數(shù)值.
物理事務的讀寫過程主要就是上面介紹的內(nèi)容,其最重要的是它的提交過程.物理事務的提交是通過mtr_commit來實現(xiàn)的.在講mtr_commit之前,先講一下,什么時候該提交,內(nèi)部是如何控制的.
這里首先需要知道的是,InnoDB的REDO日志不完全是物理日志,它包含了部分邏輯意義在里面,比如插入一行記錄時,MTR記錄的是在一個頁面中寫入這條記錄,內(nèi)容大致包括頁面號、文件號(表空間號)及這條記錄的值(包括每個列信息),這樣就有了邏輯概念.需要注意的是,在做REDO恢復時,需要保證這個頁面是正確的、完整的,不然這個REDO就會失敗,這也正是InnoDB存儲引擎中著名的DOUBLEWRITE存在的意義,不過這是后話.而如果是純物理的REDO,日志內(nèi)容應該會拆得更散,比如還是插入一條記錄,它會記錄頁面號、文件號(表空間號)、頁面內(nèi)偏移值,并且有多個這樣的REDO記錄,因為會涉及多個位置的修改操作,這就沒有任何邏輯內(nèi)容了.而針對一個插入操作,需要在一個頁面內(nèi)的不同位置寫入不同的數(shù)據(jù),當然如果是純物理REDO,相應地會產(chǎn)生多條REDO記錄,這是物理與邏輯的簡單區(qū)別.
再說MTR的提交,一個邏輯事務是由多個物理事務組成,邏輯事務是用來保證數(shù)據(jù)庫的ACID特性的,有這個就夠了,所以物理事務可以保證一次物理修改是完整的即可.所謂一次物理修改,可以理解為一個底層的相對完整的寫入操作,比如插入一條記錄的過程中,會包括寫一條回滾記錄及插入時寫入一個頁面等,那么這些邏輯上是一個動作的物理寫入,就可以被認為是一個獨立的物理事務,也就是在寫回滾記錄時執(zhí)行mtr_start,寫完之后執(zhí)行mtr_commit,真正插入時寫一個頁面也是同樣的道理.
(文字太多了,插個圖,不要太火啊!)
接著介紹MTR提交的細節(jié).物理事務的提交主要是將所有這個物理事務產(chǎn)生的日志寫入到InnoDB的日志系統(tǒng)的日志緩沖區(qū)中,然后等待srv_master_thread線程定時將日志系統(tǒng)的日志緩沖區(qū)中的日志數(shù)據(jù)刷到日志文件中,這會涉及日志刷盤時機的問題,不過還是先來看看MTR、日志緩沖區(qū)及日志文件之間的關(guān)系,如下圖所示.
從上圖中可以看出,左邊的若干個MTR產(chǎn)生了各自的REDO LOG,有些MTR已經(jīng)提交了,有些正在寫入.正在寫入日志的MTR,它們的日志都存儲在自己MTR結(jié)構(gòu)的log動態(tài)數(shù)組中,這個MTR還是不完整的,所以還是自己保存著,而對于那些已經(jīng)提交的MTR,它們對應的日志已經(jīng)在提交的時候轉(zhuǎn)存到了日志緩沖區(qū)中,相當于這些日志已經(jīng)是實實在在地產(chǎn)生了,將來必然要占用數(shù)據(jù)庫日志文件的一部分空間(除非數(shù)據(jù)庫此時掛了).
日志緩沖區(qū)的存儲只是一個暫時的中間狀態(tài),日志緩沖區(qū)的大小可以通過參數(shù)innodb_log_buffer_size來設置,一般都比較小,存儲不了太多的日志.因為已經(jīng)提交并寫入到日志緩沖的日志是確定的,所以它們是占用了LSN的,也就是說它們會使LSN變大.
最后提交的那個MTR代表著整個數(shù)據(jù)庫最新的LSN值,也就是圖中所示的Log Sequence
,這個也正是在MySQL客戶端中執(zhí)行命令
numbershow engine innodb
時,返回的信息中Log模塊中的第一行,如圖所示.
status\G
而日志緩沖區(qū)也是有大小的,當多個MTR提交時,緩沖區(qū)被占滿了,那么此時系統(tǒng)會將日志緩沖區(qū)的日志刷到日志文件中(這里涉及的另一個問題就是日志刷盤時機,這里只是一種情況,其他的后面做專門介紹),為其他新的MTR釋放空間.此時,日志的流向就是從中間的日志緩沖區(qū)向右邊的日志文件轉(zhuǎn)移,上面已經(jīng)提到過,轉(zhuǎn)移其實是平移,在緩沖區(qū)是什么內(nèi)容,寫入文件也是什么內(nèi)容,也是完全連續(xù)的,且在日志文件中,還是一個個的MTR連續(xù)存儲.
最新寫入日志文件的那個MTR產(chǎn)生的LSN值(圖中所示的log flushed up
to),其實就是上圖中展示的Log狀態(tài)的第二行,也就是日志最新的寫入文件的LSN值,這個值的意義很重大,表示的是,到這個LSN為止,所有的修改都是完整的了,如果此時數(shù)據(jù)庫掛了,寫到這個位置的數(shù)據(jù)都是可以恢復的,而不需要去關(guān)心Buffer頁面是不是被刷到磁盤.但此時在日志緩沖區(qū)中的日志所對應的操作就丟失了,這里是否會丟失事務數(shù)據(jù)與參數(shù)innodb_flush_log_at_trx_commit有關(guān)系,如果將參數(shù)innodb_flush_log_at_trx_commit設置為1,當前事務的提交肯定會將日志緩沖區(qū)中的日志刷到日志文件中;如果設置為2,那么日志只是寫入了操作系統(tǒng)緩存,并沒有寫入磁盤,那么此時有可能丟失部分已經(jīng)提交的事務,丟失多少由操作系統(tǒng)決定,這種情況下,即使數(shù)據(jù)庫掛了,只要機器不掛,就問題不大,因為操作系統(tǒng)還會將它對應的緩存寫入磁盤;但如果設置為0的話,就無能為力了,因為InnoDB只負責將事務對應的日志寫入到日志緩沖區(qū)中,無論是操作系統(tǒng),還是數(shù)據(jù)庫,都不能保證日志的安全性,所以最好不要設置成這樣.
進一步而言,日志文件的大小也是有限的,不可能無限量地將日志寫入日志文件中.前面已經(jīng)提到過,它是循環(huán)使用的,如果日志寫入的頭(圖中所示的log
flushed up to)和尾相遇了,此時日志就不能再寫入了,因為如果再寫入的話,就要“追尾”了,這樣會將之前產(chǎn)生的日志覆蓋掉,導致日志不可用,不完整.此時就會使用一種機制來保證新的日志還能繼續(xù)寫入,尾部日志還是完整的,這個機制叫做checkpoint(檢查點).
說白了,日志產(chǎn)生的作用,是將隨機頁面的寫入變成順序日志的寫入,從而用一個速度更快的寫入來保證速度較慢的寫入的完整性,從而提高整體數(shù)據(jù)庫的性能.其根本目的是要將隨機變成順序,所以日志的量才是一個相對固定循環(huán)使用的空間.有了這個思想之后,使用檢查點來保證日志的重復寫入、數(shù)據(jù)庫完整性就是順其自然的事情.
(年青俊彥單身中,歡迎留言咨詢)
使用檢查點來保證數(shù)據(jù)庫完整性的主體思想,主要是讓日志失效,也就是讓Buffer Pool中的頁面修改寫入到磁盤上面.因為日志的存在實際上就是讓Buffer
Pool中的Page盡可能少地刷磁盤,盡可能長時間地將頁面數(shù)據(jù)緩存起來,盡可能提高訪問速度,因為不管如何修改,Buffer Pool中的頁面都是最新的,只是不一定寫入磁盤中(沒有刷入沒關(guān)系,由日志來保證).如果日志文件大小不夠用,此時只要將Buffer Pool中的某些頁面刷入到磁盤中,其對應的日志就失效了,因為這些日志就是用來保證Page沒有刷入時但數(shù)據(jù)庫掛了的情況下數(shù)據(jù)庫的完整性的,而這些Page如果已經(jīng)寫入磁盤了,相應的日志也就沒有用了,這就是檢查點的根本意義所在.
而上面提到的,做檢查點時,只是將某些頁面刷入磁盤,其中的”某些”是有講究的.俗話說:“家有三件事,先從緊處來”,現(xiàn)在的問題是日志空間不夠用了,而日志是循環(huán)使用的,必須是按照順序,不能跳著寫,所以最主要的是從LSN值最小的日志開始,按照從小到大的順序不斷地讓這些日志失效.每次做檢查點都會有一個比例,此時系統(tǒng)會根據(jù)最小的有效LSN(min_valid_lsn)和檢查點處理的日志比例計算出最大的將要失效的LSN值(取名叫l(wèi)sn_checkpoint_up_to).計算完之后,再去掃描Buffer Pool的flush_list鏈表,找出所有被更新過的頁面中,曾經(jīng)修改這些頁面的MTR對應的LSN中的最小值(因為一個頁面有可能被多次修改,但只需要考慮最小的LSN的那一次,使用的是前面介紹結(jié)構(gòu)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之間的空間,又可以被重新使用了,直接覆蓋即可,而不會導致數(shù)據(jù)庫不完整、數(shù)據(jù)丟失等問題.
上面講的整個檢查點過程,用一個更形象的圖表示如下.
此時,再接著上面MTR產(chǎn)生日志的圖來講,上面找到的日志文件的位置lsn_checkpoint_up_to就是圖中所示的last checkpoint at
,也是上面命令show engine innodb status\G
中關(guān)于Log部分的第四行信息.而從這個點開始到最新的已經(jīng)刷盤的日志文件位置Log flushed up to
之間的日志都是有效日志了,不能被覆蓋,只有空間又不夠用了的情況下,再將最小的有效日志位置向前推,產(chǎn)生新的位置,像這樣不斷循環(huán),周而復始的工作,這就是日志、Buffer Pool及檢查點之間的工作原理.
上面提到的show engine innodb status\G
命令生成的Log部分中顯示的第三行信息,是Buffer Pool中Page刷盤時刷到的一個最新的LSN.但此時檢查點的最新點不一定做得及時,所以它是大于等于第四行的,而圖中所示的四行對應的值,從上到下以遞減的順序排序,其中的道理都已經(jīng)非常明確了.
上面已經(jīng)講過,物理事務和邏輯事務一樣,也是可以保證數(shù)據(jù)庫操作的完整性的.一般說來,一個操作必須要在一個物理事務中完成,也就是說要么這個操作已經(jīng)完成,要么什么也沒有做,否則就有可能造成數(shù)據(jù)不完整的問題,因為在數(shù)據(jù)庫系統(tǒng)做REDO操作時是以一個物理事務為單位做的,如果一個物理事務的日志是不完整的,則它對應的所有日志都不會重做.那么,如何辨別一個物理事務是否完整呢?這個問題是在物理事務提交時用了一個很巧妙的方法來保證的.在提交前,如果發(fā)現(xiàn)這個物理事務有日志,則在日志最后再寫一些特殊的日志,這些特殊的日志就是一個物理事務結(jié)束的標志,提交時一起將這些特殊的日志寫入,在重做時如果當前這一批日志信息最后面存在這個標志,則說明這些日志是完整的,否則就是不完整的,就不會重做.
物理事務提交時還有一項很重要的工作就是處理上面結(jié)構(gòu)體中動態(tài)數(shù)組memo中的內(nèi)容,現(xiàn)在已經(jīng)知道這個數(shù)組中存儲的是這個物理事務訪問過的所有頁面,并且都已經(jīng)上了鎖.在它提交時,如果發(fā)現(xiàn)這些頁面中已經(jīng)有被修改過的,這些頁面就成了臟頁,這些臟頁需要被加入到InnoDB Buffer Pool中的更新鏈表中(講BUFFER時已經(jīng)講過);當然,如果已經(jīng)在更新鏈中,則直接跳過(不能重復加入),svr_master_thread線程會定時檢查這個鏈表,將一定數(shù)目的臟頁刷到磁盤中,加入之后還需要將這個頁面上的鎖釋放掉,表示這個頁面已經(jīng)處理完成;如果頁面沒有被修改,或者只是用來讀取數(shù)據(jù)的,則只需要直接將其共享鎖(S鎖)釋放掉即可.
上面的內(nèi)容就是物理事務的一個完整的講述,它是比較底層的一個模塊,牽扯的東西比較多,這里重點講述了物理事務的意義、操作原理、與BUFFER系統(tǒng)的關(guān)聯(lián)、日志的產(chǎn)生等內(nèi)容.
本文就此結(jié)束,接下來繼續(xù)連載,講述有關(guān)日志的其他內(nèi)容.
文章來自微信公眾號:DBAce
轉(zhuǎn)載請注明本頁網(wǎng)址:
http://www.snjht.com/jiaocheng/4185.html