1. 程式人生 > >【MySQL技術內幕】03-Checkpoint技術

【MySQL技術內幕】03-Checkpoint技術

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

倘若每次一個頁發生變化,就將新頁的版本重新整理到磁碟,那麼這個開銷是非常大的。若熱點資料集中在某幾個頁中,那麼資料庫的效能將變得非常差。同時,如果在從緩衝池將頁的新版本重新整理到磁碟時發生了宕機,那麼資料就不能恢復了。為了避免發生資料丟失的問題,當前事務資料庫系統普遍都採用了 Write Ahead Log策略,即當事務提交時,先寫重做日誌,再修改頁。當由於發生宕機而導致資料丟失時,通過重做日誌來完成資料的恢復。這也是事務ACID中D (Durability永續性)的要求。

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

  • 緩衝池可以快取資料庫中所有的資料;
  • 重做日誌可以無限增大。

對於第一個前提條件,有經驗的使用者都知道,當資料庫剛開始建立時,表中沒有任何資料。緩衝池的確可以快取所有的資料庫檔案。然而隨著市場的推廣,使用者的增加,產品越來越受到關注,使用M也越來越大。這時負責後臺儲存的資料庫的容量必定會不斷增大。當前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 來觀察:

mysql> SHOW ENGINE INNODB STATUS\G;
...
Log sequence number 92561351052
Log flushed up to 92561351052
Last checkpoint at 92561351052
...

在InnoDB儲存引擎中,Checkpoint發生的時間、條件及髒頁的選擇等都非常複雜。

而Checkpoint所做的事情無外乎是將緩衝池中的髒頁刷回到磁碟。不同之處在於每次重新整理多少頁到磁碟,每次從哪裡取髒頁,以及什麼時間觸發Checkpoint。在IrmoDB儲存引擎內部,有兩種Checkpoint,分別為:

  • Sharp Checkpoint
  • Fuzzy Checkpoint

Sharp Checkpoint發生在資料庫關閉時將所有的髒頁都重新整理回磁碟,這是預設的工作方式,即引數 innodb_fast_shutdown=1。

但是若資料庫在執行時也使用Sharp Checkpoint,那麼資料庫的可用性就會受到很大的影響。故在IrmoDB儲存引擎內部使用Fuzzy Checkpoint進行頁的重新整理,即只重新整理一部分髒頁,而不是重新整理所有的髒頁冋磁碟。

這裡進行了概括,在InnoDB儲存引擎中可能發生如下幾種情況的Fuzzy Checkpoint:

  • Master Thread Checkpoint
  • FLUSH_LRU_LIST Checkpoint
  • Async/Sync Flush Checkpoint
  • Dirty Page too much Checkpoint

對於Master Thread中發生的Checkpoint,差不多以每秒或每十秒的速度從緩衝池的髒頁列表中重新整理一定比例的頁回磁碟。這個過程是非同步的,即此時InnoDB儲存引擎可以進行其他的操作,使用者査詢執行緒不會阻塞。

FLUSH_LRU_LIST Checkpoint是因為InnoDB儲存引擎需要保證LRU列表中需要有差不多100個空閒頁可供使用。在InnoDB 1.1.x版本之前,需要檢査LRU列表中是否有足夠的可用空間操作發生在使用者查詢執行緒中,顯然這會阻塞使用者的查詢操作。倘若沒有100個可用空閒頁,那麼IrnioDB儲存引擎會將LRU列表尾端的頁移除。如果這些頁中有髒頁,那麼需要進行Checkpoint,而這些頁是來自LRU列表的,因此稱為FLUSH_LRU_LIST Checkpoint。

而從MySQL 5.6版本,也就是InnoDB1.2.x版本開始,這個檢査被放在了一個單獨的Page Cleaner執行緒中進行,並且使用者可以通過引數innodb_lru_scan_depth控制LRU列表中可用頁的數量,該值預設為1024,如:

mysql> show variables like 'innodb_lru_scan_depth';
+-----------------------+-------+
| Variable_name         | Value |
+-----------------------+-------+
| innodb_lru_scan_depth | 1024  |
+-----------------------+-------+
1 row in set (0.01 sec)

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是為了保證重做日誌的迴圈使用的可用性。在InnoDB1.2.x版本之前,Async Flush Checkpoint會阻塞發現問題的使用者査詢執行緒,而Sync Flush Checkpoint會阻塞所有的使用者査詢執行緒,並且等待髒頁重新整理完成。從InnoDB1.2.x版本開始——也就是MySQL 5.6版本,這部分的重新整理操作同樣放入到了單獨的Page Cleaner Thread中,故不會阻塞使用者査詢執行緒。

MySQL官方版本並不能査看重新整理頁是從Hush列表中還是從LRU列表中進行Checkpoint的,也不知道因為重做日誌而產生的Async/Sync Flush的次數。但是InnoSQL版本提供了方法,可以通過命令SHOW ENGINE INNODB STATUS來觀察,如:

mysql> show engine innodb status\G
...
Async Flush: 0, Sync Flush: 0, LRU List Flush: 0, Flush List Flush: 111736
...

最後一種Checkpoint的情況是Dirty Page too much,即髒頁的數量太多,導致InnoDB儲存引擎強制進行Checkpoint。其目的總的來說還是為了保證緩衝池中有足夠可用的頁。其可由引數innodb_max_dirty_pages_pct控制:

mysql> SHOW VARIABLES LIKE 'innodb_max_dirty_pages_pct';
+----------------------------+-----------+
| Variable_name              | Value     |
+----------------------------+-----------+
| innodb_max_dirty_pages_pct | 75.000000 |
+----------------------------+-----------+
1 row in set (0.01 sec)

innodb_max_dirty__pages_pct值為75表示,當緩衝池中髒頁的數量佔據75%時,強制進行Checkpoint,重新整理一部分的髒頁到磁碟。在InnoDB 1.0.x版本之前,該引數預設值為90,之後的版本都為75。