天天看點

《MySQL技術内幕:InnoDB存儲引擎第2版》——2.4 Checkpoint技術

前面已經講到了,緩沖池的設計目的為了協調cpu速度與磁盤速度的鴻溝。是以頁的操作首先都是在緩沖池中完成的。如果一條dml語句,如update或delete改變了頁中的記錄,那麼此時頁是髒的,即緩沖池中的頁的版本要比磁盤的新。資料庫需要将新版本的頁從緩沖池重新整理到磁盤。

倘若每次一個頁發生變化,就将新頁的版本重新整理到磁盤,那麼這個開銷是非常大的。若熱點資料集中在某幾個頁中,那麼資料庫的性能将變得非常差。同時,如果在從緩沖池将頁的新版本重新整理到磁盤時發生了當機,那麼資料就不能恢複了。為了避免發生資料丢失的問題,目前事務資料庫系統普遍都采用了write ahead log政策,即當事務送出時,先寫重做日志,再修改頁。當由于發生當機而導緻資料丢失時,通過重做日志來完成資料的恢複。這也是事務acid中d(durability持久性)的要求。

思考下面的場景,如果重做日志可以無限地增大,同時緩沖池也足夠大,能夠緩沖所有資料庫的資料,那麼是不需要将緩沖池中頁的新版本重新整理回磁盤。因為當發生當機時,完全可以通過重做日志來恢複整個資料庫系統中的資料到當機發生的時刻。但是這需要兩個前提條件:

?緩沖池可以緩存資料庫中所有的資料;

?重做日志可以無限增大。

對于第一個前提條件,有經驗的使用者都知道,當資料庫剛開始建立時,表中沒有任何資料。緩沖池的确可以緩存所有的資料庫檔案。然而随着市場的推廣,使用者的增加,産品越來越受到關注,使用量也越來越大。這時負責背景存儲的資料庫的容量必定會不斷增大。目前3tb的mysql資料庫已并不少見,但是3?tb的記憶體卻非常少見。目前oracle exadata旗艦資料庫一體機也就隻有2?tb的記憶體。是以第一個假設對于生産環境應用中的資料庫是很難得到保證的。

再來看第二個前提條件:重做日志可以無限增大。也許是可以的,但是這對成本的要求太高,同時不便于運維。dba或sa不能知道什麼時候重做日志是否已經接近于磁盤可使用空間的門檻值,并且要讓儲存設備支援可動态擴充也是需要一定的技巧和裝置支援的。

好的,即使上述兩個條件都滿足,那麼還有一個情況需要考慮:當機後資料庫的恢複時間。當資料庫運作了幾個月甚至幾年時,這時發生當機,重新應用重做日志的時間會非常久,此時恢複的代價也會非常大。

是以checkpoint(檢查點)技術的目的是解決以下幾個問題:

?縮短資料庫的恢複時間;

?緩沖池不夠用時,将髒頁重新整理到磁盤;

?重做日志不可用時,重新整理髒頁。

當資料庫發生當機時,資料庫不需要重做所有的日志,因為checkpoint之前的頁都已經重新整理回磁盤。故資料庫隻需對checkpoint後的重做日志進行恢複。這樣就大大縮短了恢複的時間。

此外,當緩沖池不夠用時,根據lru算法會溢出最近最少使用的頁,若此頁為髒頁,那麼需要強制執行checkpoint,将髒頁也就是頁的新版本刷回磁盤。

重做日志出現不可用的情況是因為目前事務資料庫系統對重做日志的設計都是循環使用的,并不是讓其無限增大的,這從成本及管理上都是比較困難的。重做日志可以被重用的部分是指這些重做日志已經不再需要,即當資料庫發生當機時,資料庫恢複操作不需要這部分的重做日志,是以這部分就可以被覆寫重用。若此時重做日志還需要使用,那麼必須強制産生checkpoint,将緩沖池中的頁至少重新整理到目前重做日志的位置。

對于innodb存儲引擎而言,其是通過lsn(log sequence number)來标記版本的。而lsn是8位元組的數字,其機關是位元組。每個頁有lsn,重做日志中也有lsn,checkpoint也有lsn。可以通過指令show engine innodb status來觀察:

在innodb存儲引擎中,checkpoint發生的時間、條件及髒頁的選擇等都非常複雜。而checkpoint所做的事情無外乎是将緩沖池中的髒頁刷回到磁盤。不同之處在于每次重新整理多少頁到磁盤,每次從哪裡取髒頁,以及什麼時間觸發checkpoint。在innodb存儲引擎内部,有兩種checkpoint,分别為:

sharp checkpoint發生在資料庫關閉時将所有的髒頁都重新整理回磁盤,這是預設的工作方式,即參數innodb_fast_shutdown=1。

但是若資料庫在運作時也使用sharp checkpoint,那麼資料庫的可用性就會受到很大的影響。故在innodb存儲引擎内部使用fuzzy checkpoint進行頁的重新整理,即隻重新整理一部分髒頁,而不是重新整理所有的髒頁回磁盤。

這裡筆者進行了概括,在innodb存儲引擎中可能發生如下幾種情況的fuzzy checkpoint:

對于master thread(2.5節會詳細介紹各個版本中master thread的實作)中發生的checkpoint,差不多以每秒或每十秒的速度從緩沖池的髒頁清單中重新整理一定比例的頁回磁盤。這個過程是異步的,即此時innodb存儲引擎可以進行其他的操作,使用者查詢線程不會阻塞。

flush_lru_list checkpoint是因為innodb存儲引擎需要保證lru清單中需要有差不多100個空閑頁可供使用。在innodb1.1.x版本之前,需要檢查lru清單中是否有足夠的可用空間操作發生在使用者查詢線程中,顯然這會阻塞使用者的查詢操作。倘若沒有100個可用空閑頁,那麼innodb存儲引擎會将lru清單尾端的頁移除。如果這些頁中有髒頁,那麼需要進行checkpoint,而這些頁是來自lru清單的,是以稱為flush_lru_list checkpoint。

而從mysql 5.6版本,也就是innodb1.2.x版本開始,這個檢查被放在了一個單獨的page cleaner線程中進行,并且使用者可以通過參數innodb_lru_scan_depth控制lru清單中可用頁的數量,該值預設為1024,如:

async/sync flush checkpoint指的是重做日志檔案不可用的情況,這時需要強制将一些頁重新整理回磁盤,而此時髒頁是從髒頁清單中選取的。若将已經寫入到重做日志的lsn記為redo_lsn,将已經重新整理回磁盤最新頁的lsn記為checkpoint_lsn,則可定義:

再定義以下的變量:

若每個重做日志檔案的大小為1gb,并且定義了兩個重做日志檔案,則重做日志檔案的總大小為2gb。那麼async_water_mark=1.5gb,sync_water_mark=1.8gb。則:

?當checkpoint_age?當async_water_mark?checkpoint_age>sync_water_mark這種情況一般很少發生,除非設定的重做日志檔案太小,并且在進行類似load data的bulk insert操作。此時觸發sync flush操作,從flush清單中重新整理足夠的髒頁回磁盤,使得重新整理後滿足checkpoint_age可見,async/sync flush checkpoint是為了保證重做日志的循環使用的可用性。在innodb 1.2.x版本之前,async flush checkpoint會阻塞發現問題的使用者查詢線程,而sync flush checkpoint會阻塞所有的使用者查詢線程,并且等待髒頁重新整理完成。從innodb 1.2.x版本開始——也就是mysql 5.6版本,這部分的重新整理操作同樣放入到了單獨的page cleaner thread中,故不會阻塞使用者查詢線程。

mysql官方版本并不能檢視重新整理頁是從flush清單中還是從lru清單中進行checkpoint的,也不知道因為重做日志而産生的async/sync flush的次數。但是innosql版本提供了方法,可以通過指令show engine innodb status來觀察,如:

根據上述的資訊,還可以對innodb存儲引擎做更為深入的調優,這部分将在第9章中講述。

最後一種checkpoint的情況是dirty page too much,即髒頁的數量太多,導緻innodb存儲引擎強制進行checkpoint。其目的總的來說還是為了保證緩沖池中有足夠可用的頁。其可由參數innodb_max_dirty_pages_pct控制:

innodb_max_dirty_pages_pct值為75表示,當緩沖池中髒頁的數量占據75%時,強制進行checkpoint,重新整理一部分的髒頁到磁盤。在innodb 1.0.x版本之前,該參數預設值為90,之後的版本都為75。