MySQL教程:關於checkpoint機制
一、簡介
思考一下這個情境:如果重做日誌可以無限地增大,同時緩衝池也足夠大,那麼是不需要將緩衝池中頁的新版本重新整理回磁碟。因為當發生宕機時,完全可以通過重做日誌來恢複整個資料庫系統中的資料到宕機發生的時刻。
但是這需要兩個前提條件:1、緩衝池可以快取資料庫中所有的資料;2、重做日誌可以無限增大
因此Checkpoint(檢查點)技術就誕生了,目的是解決以下幾個問題:1、縮短資料庫的恢復;2、緩衝池不夠用時,將髒頁重新整理到磁碟;3、重做日誌不可用時,重新整理髒頁。
- 當資料庫發生宕機時,資料庫不需要重做所有的日誌,因為Checkpoint之前的頁都已經重新整理回磁碟。資料庫只需對Checkpoint後的重做日誌進行恢複,這樣就大大縮短了恢複的時間。
- 當緩衝池不夠用時,根據LRU演算法會溢出最近最少使用的頁,若此頁為髒頁,那麼需要強制執行Checkpoint,將髒頁也就是頁的新版本刷回磁碟。
- 當重做日誌出現不可用時,因為當前交易資料庫系統對重做日誌的設計都是迴圈使用的,並不是讓其無限增大的,重做日誌可以被重用的部分是指這些重做日誌已經不再需要,當資料庫發生宕機時,資料庫恢複操作不需要這部分的重做日誌,因此這部分就可以被覆蓋重用。如果重做日誌還需要使用,那麼必須強制Checkpoint,將緩衝池中的頁至少重新整理到當前重做日誌的位置。
對於InnoDB儲存引擎而言,是通過LSN(Log Sequence Number)來標記版本的。
LSN是8位元組的數字,每個頁有LSN,重做日誌中也有LSN,Checkpoint也有LSN。可以通過命令SHOW ENGINE INNODB STATUS來觀察:
mysql> show engine innodb status \G
---
LOG
---
Log sequence number 34778380870
Log flushed up to 34778380870
Last checkpoint at 34778380870
0 pending log writes, 0 pending chkp writes
54020151 log i/o's done, 0.92 log i/o's/second
Checkpoint發生的時間、條件及髒頁的選擇等都非常複雜。而Checkpoint所做的事情無外乎是將緩衝池中的髒頁刷回到磁碟,不同之處在於每次重新整理多少頁到磁碟,每次從哪裡取髒頁,以及什麼時間觸發Checkpoint。
二、Checkpoint分類
在InnoDB儲存引擎內部,有兩種Checkpoint,分別為:Sharp Checkpoint、Fuzzy Checkpoint
Sharp Checkpoint 發生在資料庫關閉時將所有的髒頁都重新整理回磁碟,這是預設的工作方式,即參數innodb_fast_shutdown=1。但是若資料庫在運行時也使用Sharp Checkpoint,那麼資料庫的可用性就會受到很大的影響。故在InnoDB儲存引擎內部使用Fuzzy Checkpoint進行頁的重新整理,即只重新整理一部分髒頁,而不是重新整理所有的髒頁回磁碟。
Fuzzy Checkpoint:1、Master Thread Checkpoint;2、FLUSH_LRU_LIST Checkpoint;3、Async/Sync Flush Checkpoint;4、Dirty Page too much Checkpoint
1、Master Thread Checkpoint
以每秒或每十秒的速度從緩衝池的髒頁列表中重新整理一定比例的頁回磁碟,這個過程是非同步,此時InnoDB儲存引擎可以進行其他的操作,使用者查詢線程不會阻塞。
2、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,如:
mysql> SHOW GLOBAL VARIABLES LIKE 'innodb_lru_scan_depth';
+-----------------------+-------+
| Variable_name | Value |
+-----------------------+-------+
| innodb_lru_scan_depth | 1024 |
+-----------------------+-------+
3、Async/Sync Flush Checkpoint
指的是重做記錄檔停用情況,這時需要強制將一些頁重新整理回磁碟,而此時髒頁是從髒頁列表中選取的。若將已經寫入到重做日誌的LSN記為redo_lsn,將已經重新整理回磁碟最新頁的LSN記為checkpoint_lsn,則可定義:
checkpoint_age = redo_lsn - checkpoint_lsn
再定義以下的變數:
async_water_mark = 75% * total_redo_log_file_size
sync_water_mark = 90% * total_redo_log_file_size
若每個重做記錄檔的大小為1GB,並且定義了兩個重做記錄檔,則重做記錄檔的總大小為2GB。那麼async_water_mark=1.5GB,sync_water_mark=1.8GB。則:
當checkpoint_age<async_water_mark時,不需要重新整理任何髒頁到磁碟;
當async_water_mark<checkpoint_age<sync_water_mark時觸發Async Flush,從Flush列表中重新整理足夠的髒頁回磁碟,使得重新整理後滿足checkpoint_age<async_water_mark;
checkpoint_age>sync_water_mark這種情況一般很少發生,除非設定的重做記錄檔太小,並且在進行類似LOAD DATA的BULK INSERT操作。此時觸發Sync Flush操作,從Flush列表中重新整理足夠的髒頁回磁碟,使得重新整理後滿足checkpoint_age<async_water_mark。
可見,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來觀察,如:
mysql> show engine innodb status \G
BUFFER POOL AND MEMORY
----------------------
Total memory allocated 2058485760; in additional pool allocated 0
Dictionary memory allocated 913470
Buffer pool size 122879
Free buffers 79668
Database pages 41957
Old database pages 15468
Modified db pages 0
Pending reads 0
Pending writes: LRU 0, flush list 0, single page 0
Pages made young 15032929, not young 0
0.00 youngs/s, 0.00 non-youngs/s
Pages read 15075936, created 366872, written 36656423
0.00 reads/s, 0.00 creates/s, 0.90 writes/s
Buffer pool hit rate 1000 / 1000, young-making rate 0 / 1000 not 0 / 1000
Pages read ahead 0.00/s, evicted without access 0.00/s, Random read ahead 0.00/s
LRU len: 41957, unzip_LRU len: 0
I/O sum[39]:cur[0], unzip sum[0]:cur[0]
4、Dirty Page too much
即髒頁的數量太多,導致InnoDB儲存引擎強制進行Checkpoint。其目的總的來說還是為了保證緩衝池中有足夠可用的頁。其可由參數innodb_max_dirty_pages_pct控制:
mysql> SHOW GLOBAL VARIABLES LIKE 'innodb_max_dirty_pages_pct' ;
+----------------------------+-------+
| Variable_name | Value |
+----------------------------+-------+
| innodb_max_dirty_pages_pct | 75 |
+----------------------------+-------+
innodb_max_dirty_pages_pct值為75表示,當緩衝池中髒頁的數量佔據75%時,強制進行Checkpoint,重新整理一部分的髒頁到磁碟。在InnoDB 1.0.x版本之前,該參數預設值為90,之後的版本都為75。
三、Checkpoint機制
在Innodb交易記錄中,採用了Fuzzy Checkpoint,Innodb每次取最老的modified page(last checkpoint)對應的LSN,再將此髒頁的LSN作為Checkpoint點記錄到記錄檔,意思就是“此LSN之前的LSN對應的日誌和資料都已經flush到redo log
當mysql crash的時候,Innodb掃描redo log,從last checkpoint開始apply redo log到buffer pool,直到last checkpoint對應的LSN等於Log flushed up to對應的LSN,則恢複完成
那麼具體是怎麼恢複的呢?
如所示,Innodb的一條交易記錄共經曆4個階段:
- 建立階段:事務建立一條日誌;
- 日誌刷盤:日誌寫入到磁碟上的記錄檔;
- 資料刷盤:日誌對應的髒頁資料寫入到磁碟上的資料檔案;
- 寫CKP:日誌被當作Checkpoint寫入記錄檔;
對應這4個階段,系統記錄了4個日誌相關的資訊,用於其它各種處理使用:
- Log sequence number(LSN1):當前系統LSN最大值,新的交易記錄LSN將在此基礎上產生(LSN1+新日誌的大小);
- Log flushed up to(LSN2):當前已經寫入記錄檔的LSN;
- Oldest modified data log(LSN3):當前最舊的髒頁資料對應的LSN,寫Checkpoint的時候直接將此LSN寫入到記錄檔;
- Last checkpoint at(LSN4):當前已經寫入Checkpoint的LSN;
對於系統來說,以上4個LSN是遞減的,即: LSN1>=LSN2>=LSN3>=LSN4.
具體的範例如下(使用show innodb status \G命令查看,Oldest modified data log沒有顯示):
LOG
---
Log sequence number 34822137537
Log flushed up to 34822137537
Last checkpoint at 34822133028
0 pending log writes, 0 pending chkp writes
54189288 log i/o's done, 3.00 log i/o's/second
四、日誌保護機制
mysql crash的時候,Innodb有日誌刷盤機制,可以通過innodb_flush_log_at_trx_commit參數進行控制,這裡說的是如何防止日誌覆蓋導致日誌丟失
Innodb的checkpoint和redo log有哪些緊密關係?有几上名詞需要解釋一下:
- Ckp age(動態移動): 最老的dirty page還沒有flush到資料檔案,即沒有做last checkpoint的範圍
- Buf age(動態移動): modified page information沒有寫到log中,但已在log buffer
- Buf async(固定點): 日誌空間大小的7/8,當buf age移動到Buf async點時,強制把沒有寫到log中的modified page information開始寫入到log中,不阻塞事務
- Buf sync(固定點): 日誌空間大小的15/16,當寫入很大的,buf age移動非常快,一下子到buf sync的點,阻塞事務,強制把modified page information開始寫入到log中。如果不阻塞事務,未做last checkpoint的redo log存在覆蓋危險
- Ckp async(固定點): 日誌空間大小的31/32,當ckp age到達ckp async,強製做last checkpoint,不阻塞事務
- Ckp sync(固定點):日誌空間大小,當ckp age到達ckp sync,強製做last checkpoint,阻塞事務,存在redo log覆蓋的危險
接下分析4種情況
- 如果buf age在buf async和buf sync之間
- 如果buf age在buf sync之後(當然這種情況是不存在,mysql有保護機制)
- 如果ckp age在ckp async和ckp sync之間(這種情況是不存在)
- 如果ckp age在ckp sync之後(這種情況是不存在)
第一種情況:
當寫入量巨大時,buf age移動到buf async和buf sync之間,觸發寫出到log中,mysql把盡量多的log寫出,如果寫入量減慢,buf age又移回到“圖一”狀態。如果寫入量大於flush log的速度,buf age最終會和buf sync重疊,這時所有的事務都被阻塞,強制將2*(Buf age-Buf async)的髒頁刷盤,這時IO會比較繁忙。
第二種情況:
當然這種情況是不可能出現,因為如果出現,redo log存在覆蓋的可能,資料就會丟失。buf age會越過log size,buf age的大小可能就超過log size,如果要刷buf age,那麼整個log size都不夠容納所有的buf age。
第三種和第四種情況不存在分析:
ckp age始終位於buf age的後面(左邊),因為ckp age是last checkpoint點,總是追趕buf age(將儘可能多的modified page flush到磁碟),所以buf age肯定是先到達到buf sync。
ckp async及ckp sync存在意義?
mysql中page cache也存在high water及low water,當dirty page觸到low water時,os是開始flush dirty page到磁碟,到high water時,會阻塞一切動作,os會瘋狂的flush dirty page,磁碟會很忙,存在IO Storm,
本文永久更新連結地址: