MySQL WAL(Write-Ahead Log)機制及髒頁刷新

最後更新: 2019年10月28日13:35:41

本篇文章屬於個人備忘錄, 主要內容來自: 極客時間《MySQL實戰45講》的第12講 - 爲什麼我的MySQL會“抖”一下

WAL(Write-Ahead Loggin)

WAL 是預寫式日誌, 關鍵點在於先寫日誌再寫磁盤.

在對數據頁進行修改時, 通過將"修改了什麼"這個操作記錄在日誌中, 而不必馬上將更改內容刷新到磁盤上, 從而將隨機寫轉換爲順序寫, 提高了性能.

但由此帶來的問題是, 內存中的數據頁會和磁盤上的數據頁內容不一致, 此時將內存中的這種數據頁稱爲 髒頁

Redo Log(重做日誌)

這裏的日誌指的是Redo Log(重做日誌), 這個日誌是循環寫入的.

它記錄的是在某個數據頁上做了什麼修改, 這個日誌會攜帶一個LSN, 同時每個數據頁上也會記錄一個LSN(日誌序列號).

這個日誌序列號(LSN)可以用於數據頁是否是髒頁的判斷, 比如說 write pos對應的LSN比某個數據頁的LSN大, 則這個數據頁肯定是乾淨頁, 同時當髒頁提前刷到磁盤時, 在應用Redo Log可以識別是否刷過並跳過.

這裏有兩個關鍵位置點:

  • write pos 當前記錄的位置, 一邊寫以便後移.
  • checkpoint 是當前要擦除的位置, 擦除記錄前要把記錄更新到數據文件.

髒頁

當內存數據頁和磁盤數據頁內容不一致的時候, 將內存頁稱爲"髒頁".
內存數據頁寫入磁盤後, 兩邊內容一致, 此時稱爲"乾淨頁".
將內存數據頁寫入磁盤的這個操作叫做"刷髒頁"(flush).

InnoDB是以緩衝池(Buffer Pool)來管理內存的, 緩衝池中的內存頁有3種狀態:

  • 未被使用
  • 已被使用, 並且是乾淨頁
  • 已被使用, 並且是髒頁

由於InnoDB的策略通常是儘量使用內存, 因此長時間運行的數據庫中的內存頁基本都是被使用的, 未被使用的內存頁很少.

刷髒頁(flush)

時機

刷髒頁的時機:

  1. Redo Log寫滿了, 需要將 checkpoint 向前推進, 以便繼續寫入日誌

    checkpoint 向前推進時, 需要將推進區間涉及的所有髒頁刷新到磁盤.

  2. 內存不足, 需要淘汰一些內存頁(最久未使用的)給別的數據頁使用.

    此時如果是乾淨頁, 則直接拿來複用.

    如果是髒頁, 則需要先刷新到磁盤(直接寫入磁盤, 不用管Redo Log, 後續Redo Log刷髒頁時會判斷對應數據頁是否已刷新到磁盤), 使之成爲乾淨頁再拿來使用.

  3. 數據庫系統空閒時

    當然平時忙的時候也會盡量刷髒頁.

  4. 數據庫正常關閉

    此時需要將所有髒頁刷新到磁盤.

InnoDB需要控制髒頁比例來避免Redo Log寫滿以及單次淘汰過多髒頁過多的情況.

Redo Log 寫滿

這種情況儘量避免, 因此此時系統就不接受更新, 所有更新語句都會被堵住, 此時更新數爲0.

對於敏感業務來說, 這是不能接受的.

此時需要將 write pos 向前推進, 推進範圍內Redo Log涉及的所有髒頁都需要flush到磁盤中.

Redo Log設置過小或寫太慢的問題: 此時由於Redo Log頻繁寫滿, 會導致頻繁觸發flush髒頁, 影響tps.

內存不足

這種情況其實是常態.

當從磁盤讀取的數據頁在內存中沒有內存時, 就需要到緩衝池中申請一個內存頁, 這時候根據LRU(最久不使用)就需要淘汰掉一個內存頁來使用.

此時淘汰的是髒頁, 則需要將髒頁刷新到磁盤, 變成乾淨頁後才能複用.

注意, 這個過程 Write Pos 位置是不會向前推進的.

當一個查詢要淘汰的髒頁數太多, 會導致查詢的響應時間明顯變長.

策略

InnoDB 控制刷髒頁的策略主要參考:

  • 髒頁比例

    當髒頁比例接近或超過參數 innodb_max_dirty_pages_pct 時, 則會全力, 否則按照百分比.

  • redo log 寫盤速度

    N = (write pos 位置的日誌序號 - checkpoint對應序號), 當N越大, 則刷盤速度越快.

最終刷盤速度取上述兩者中最快的.

參數 innodb_io_capacity

InnoDB 有一個關鍵參數: innodb_io_capacity, 該參數是用於告知InnoDB你的磁盤能力, 該值通常建議設置爲磁盤的寫IOPS.

該參數在 MySQL 5.5 及後續版本纔可以調整.

測試磁盤的IOPS:

fio -filename=/data/tmp/test_randrw -direct=1 -iodepth 1 -thread -rw=randrw -ioengine=psync -bs=16k -size=500M -numjobs=10 -runtime=10 -group_reporting -name=mytest
注意, 上面的 -filename 要指定具體的文件名, 千萬不要指定分區, 否則會導致分區不可用, 需要重新格式化.

innodb_io_capacity 一般參考 寫能力的IOPS

innodb_io_capacity 設置過低導致的性能問題案例:

MySQL寫入速度很慢, TPS很低, 但是數據庫主機的IO壓力並不大.

innodb_io_capacity 設置過小時, InnoDB會認爲磁盤性能差, 導致刷髒頁很慢, 甚至比髒頁生成速度還慢, 就會造成髒頁累積, 影響查詢和更新性能.

innodb_io_capacity 大小設置:

  • 配置小, 此時由於InnoDB認爲你的磁盤性能差, 因此刷髒頁頻率會更高, 以此來確保內存中的髒頁比例較少.
  • 配置大, InnoDB認爲磁盤性能好, 因此刷髒頁頻率會降低, 抖動的頻率也會降低.

參數innodb_max_dirty_pages_pct

innodb_max_dirty_pages_pct 指的是髒頁比例上限(默認值是75%), 內存中的髒頁比例越是接近該值, 則InnoDB刷盤速度會越接近全力.

如何計算內存中的髒頁比例:

show global status like 'Innodb_buffer_pool_pages%';

髒頁比例 = 100 * Innodb_buffer_pool_pages_dirty / Innodb_buffer_pool_pages_total 的值

參數 innodb_flush_neighbors

當刷髒頁時, 若髒頁旁邊的數據頁也是髒頁, 則會連帶刷新, 注意這個機制是會蔓延的.

innodb_flush_neighbors=1 時開啓該機制, 默認是1, 但在 MySQL 8.0 中默認值是 0.

由於機械硬盤時代的IOPS一般只有幾百, 該機制可以有效減少很多隨機IO, 提高系統性能.

但在固態硬盤時代, 此時IOPS高達幾千, 此時IOPS往往不是瓶頸, "只刷自己"可以更快執行完查詢操作, 減少SQL語句的響應時間.

如果Redo Log 設置太小

這裏有一個案例:

測試在做壓力測試時, 剛開始 insert, update 很快, 但是一會就變慢且響應延遲很高.

↑ 出現這種情況大部分是因爲 Redo Log 設置太小引起的.

因爲此時 Redo Log 寫滿後需要將 checkpoint 前推, 此時需要刷髒頁, 可能還會連坐(innodb_flush_neighbors=1), 數據庫"抖"的頻率變高.

其實此時內存的髒頁比例可能還很低, 並沒有充分利用到大內存優勢, 此時需要頻繁flush, 性能會變差.

同時, 如果Redo Log中存在change buffer, 同樣需要做相應的merge操作, 導致 change buffer 發揮不出作用.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章