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

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

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

WAL(Write-Ahead Loggin)

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

在對數據頁進行修改時, 經過將"修改了什麼"這個操做記錄在日誌中, 而沒必要立刻將更改內容刷新到磁盤上, 從而將隨機寫轉換爲順序寫, 提升了性能.測試

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

Redo Log(重作日誌)

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

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

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

這裏有兩個關鍵位置點:it

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

髒頁

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

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 發揮不出做用.

相關文章
相關標籤/搜索