Ⅰ、Checkpoint
1.1 checkpoint的作用
- 縮短數據庫的恢復時間
- 緩沖池不夠用時,將臟頁刷到磁盤
- 重做日志不可用時,刷新臟頁

1.2 展開分析
page被緩存在bp中,page在bp中和disk中不是時刻保持一致的(page修改一下就刷一次盤是不現實的,是通過checkpoint來玩的)
萬一宕機,重啟的時候disk上那個page需要恢復到原來bp中page的那個版本
那問題是,兩個page版本不一致咋整?沒事,我們做到最終一致就行
那我們就說一下這個最終一致是個怎樣的過程,通過一個例子來說明:

Step1:
一個page讀到bp中時,它的lsn(這個鬼東西待會兒仔細說,先理解為一個flag)是100,然后這個page被modify了,它的lsn變成了130,當對應的事務提交后,修改日志會被記錄到redo里面,此時redo和全局的lsn就相應的變成了130
Step2:
另外一個page之前進bp的時候lsn是50,前面那個page被modify之后,它也被修改,它的lsn變成了140,它這個140的lsn也寫到了redo里,全局lsn變成140
Step3:
關鍵的一步,假設此時lsn為130的page被刷到disk上了(什么時候刷也是個學問,這里不說),而lsn為140的那個page還沒被刷,磁盤上保存的還是老版本,突然宕機了。
Step4:
這時候restart數據庫,就會從磁盤上cp的位置(130)開始讀redo log,一直回放到140,這樣沒被刷到磁盤的那個page就恢復到宕機之前的狀態了。
划重點:
①這個130,140其實就是字節數,也就是說你對這個頁修改產生了10個字節的日志,那么lsn就加10
②page原來讀進bp的lsn甭管,只管它改變了多少字節就行,所以這個lsn的變化肯定是一個單調遞增的過程,其實lsn就是日志寫了多少字節(之前沒理解好,以為各個page的lsn是自己玩自己的)
Ⅱ、LSN(log sequence number)——日志序列號
lsn是用來保存checkpoint的,保存現在刷新到磁盤的位置在哪里
這個130,140其實就是字節數,也就是說你對這個頁修改產生了10個字節的日志,那么lsn就加10,lsn沒有上限,8字節
2.1 lsn存在什么地方?
- 每個page有一個LSN,page更新一下LSN就會更新一下,記錄在page header中
- 整個MySQL實例也有一個LSN(這就是checkpoint),記錄在第一個重做日志的前2k的塊里(就給它用,不會被覆蓋)
- redo log里有一個LSN
全局lsn位置之前的內容已經刷磁盤上,只要恢復它后面的日志,數據就恢復了
2.2 查看lsn和整個checkpoint流程梳理
看page中的lsn,page中其實是保存兩個lsn的,如下:
(root@172.16.0.10) [(none)]> desc information_schema.INNODB_BUFFER_PAGE_LRU;
+---------------------+---------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------------------+---------------------+------+-----+---------+-------+
| POOL_ID | bigint(21) unsigned | NO | | 0 | |
| LRU_POSITION | bigint(21) unsigned | NO | | 0 | |
| SPACE | bigint(21) unsigned | NO | | 0 | |
| PAGE_NUMBER | bigint(21) unsigned | NO | | 0 | |
| PAGE_TYPE | varchar(64) | YES | | NULL | |
| FLUSH_TYPE | bigint(21) unsigned | NO | | 0 | |
| FIX_COUNT | bigint(21) unsigned | NO | | 0 | |
| IS_HASHED | varchar(3) | YES | | NULL | |
| NEWEST_MODIFICATION | bigint(21) unsigned | NO | | 0 | |
| OLDEST_MODIFICATION | bigint(21) unsigned | NO | | 0 | |
| ACCESS_TIME | bigint(21) unsigned | NO | | 0 | |
| TABLE_NAME | varchar(1024) | YES | | NULL | |
| INDEX_NAME | varchar(1024) | YES | | NULL | |
| NUMBER_RECORDS | bigint(21) unsigned | NO | | 0 | |
| DATA_SIZE | bigint(21) unsigned | NO | | 0 | |
| COMPRESSED_SIZE | bigint(21) unsigned | NO | | 0 | |
| COMPRESSED | varchar(3) | YES | | NULL | |
| IO_FIX | varchar(64) | YES | | NULL | |
| IS_OLD | varchar(3) | YES | | NULL | |
| FREE_PAGE_CLOCK | bigint(21) unsigned | NO | | 0 | |
+---------------------+---------------------+------+-----+---------+-------+
20 rows in set (0.00 sec)
newest_modification 頁最新更新完后的lsn
oldest_modification 頁第一次更新完后的lsn
page刷到磁盤的時候,全局的check_point保存的是oldest(只保存第一次修改時的lsn),而page中的lsn保存的是newest
(root@172.16.0.10) [(none)]> show engine innodb status\G
...
---
LOG
---
Log sequence number 15151135824 當前內存中最新的LSN
Log flushed up to 15151135824 redo刷到磁盤的LSN
Pages flushed up to 15151135824 最后一個刷到磁盤上的頁的最新的LSN(NEWEST_MODIFICATION)
Last checkpoint at 15151135815 最后一個刷到磁盤上的頁的第一次被修改時的LSN(OLDEST_MODIFICATION)
...
Log sequence number和Log flushed up這兩個LSN可能會不同,運行過程中后者可能會小於 前者,因為redo日志也是先在內存中更新,再刷到磁盤的
最后一個小於前面三個,為什么?
臟頁會被指向flush list這個就不多贅述了
flush list是根據lsn進行組織的,而且還是用一個page第一次放進來的lsn進行組織的,也就是說這個page再次發生更新,它的位置是不會移動的
分析一波:
bp的LRU列表中,一個page,假設LSN進來的時候是100,當前全局LSN也是100,如果這個page變化了,產生了20字節的日志,這時候page的lsn變成120,並且通過指針指向flush list中去了,但是這個page立馬又被更新產生20字節日志,此時page的lsn為140,而此時在flush list中的lsn還是120(這里意思就是page里面保存了兩種lsn,一個是第一次修改頁的,一個是最后一次修改頁的)
當這個lsn為120的page被刷到disk上,那么disk上的cp就是120了,但是上面的三個值都是140,是不是很好理解呢,那就是說,每個page只更新一次,那這四個值就相等了唄,23333!
為什么這么設計?
為了恢復的時候,保證redo回放的過程的連續性,不會出錯
page A第一次修改后lsn是120,記錄到全局lsn,后面還有個page B被更新,lsn變為140,此時,page A再更新,lsn變為160了。這時候發生宕機,page A被刷到磁盤,page B沒刷過去,如果flush list里面記錄160的話,發生故障重啟時lsn為140的page B怎么恢復?是不是被跳過去了
那從120開始恢復,那個頁已經是160了,為什么還要恢復?
數據庫會檢測,如果page的lsn大於實例的lsn,就不會恢復這個page,跨過去,只將page B從120恢復到140
tips:
①checkpoint不需要實時刷新到磁盤,不是一個頁更新了就要更新磁盤上的cp,磁盤上的cp前置一點是沒有關系的,大不了多scan一點redo log,讀到不回放就是了,而是由master_thread控制,差不多每秒鍾更新一次
②回滾問題
回滾不是通過redo來回滾的,所有的page前滾到一個位置(恢復完),這些page對應的事務還是活躍的,還沒提交,之后這些事務都會通過undo log來undo回滾,但undo是通過redo來恢復的
比如一個頁120-160已經恢復過去了,但是這個事務需要回滾,卻又已經刷到磁盤了,沒關系,通過undo log往回滾一下就好了
事務活躍列表存放在undo段中,只要事務沒提交就在里面,提交后移動到undo的history中,這個歷史列表是用來做purge的,這里面的undo會被慢慢回收
Ⅲ、checkpoint 分類
- Sharp Checkpoint
將所有的贓頁都刷新回磁盤,刷新時系統hang住,InnoDB關閉時使用
相關參數:innodb_fast_shutdown={1|0} - Fuzzy Checkpoint
將部分臟頁刷新回磁盤,對系統影響較小
innodb_io_capacity來控制,最小限制為100,表示一次最多刷新臟頁的能力,與IOPS相關
SSD可以設置在4000-8000,SAS最多設置在800多(IOPS在1000左右)
Ⅳ、什么時候刷dirty page
-
以前在master thread線程中(從flush_list中進行刷新)
現在都在page_cleaner_thread線程中(每一秒,每十秒)
-
FLUSH_LRU_LIST 刷新
5.5以前需要保證在LRU_LIST尾部要有100個空閑頁(可替換的頁),即刷新一部分數據 ,保證有100個空閑頁。
由innodb_lru_scan_depth參數來控制,並不只是刷最后一個頁,默認探測尾部1024個頁(默認),1024個頁中所有臟頁會一起刷掉,該參數是應用到每個Buffer Pool,總數即為該值乘以Buffer Pool的個數,總量超過innodb_io_capacity是不合理的,即此參數不得超過innodb_io_capacity/innodb_buffer_pool_instances,ssd的話,可以適當把這個掃描深度調深一點
-
Async/Sync Flush Checkpoint
重做日志重用 -
Dirty Page too much
贓頁比例超過bp總量的一定比例,本來是通過page_cleaner_thread來刷,但是臟頁太多了,就會強行刷,由innodb_max_dirty_pages_pct參數控制
tips:
①頁只會從flush_list中刷新這個觀點是不對的,只有page_cleaner_thread定期問flush_list要臟頁,一個一個刷,刷到innodb_io_capacity的比例值
②LRU list中既存在干凈的頁也存在臟頁,假設最后一個頁,是臟的,另一個線程需要一個頁,free list已經空了,lru會把這個頁淘汰給這個線程去使用,這時候也需要刷新這個臟頁,默認一下探測1024個page,把臟頁刷掉
