最後更新: 2019年10月28日13:35:41sql
本篇文章屬於我的備忘錄, 主要內容來自: 極客時間《MySQL實戰45講》的第12講 - 爲何個人MySQL會「抖」一下數據庫
WAL 是預寫式日誌, 關鍵點在於先寫日誌再寫磁盤.性能
在對數據頁進行修改時, 經過將"修改了什麼"這個操做記錄在日誌中, 而沒必要立刻將更改內容刷新到磁盤上, 從而將隨機寫轉換爲順序寫, 提升了性能.測試
但由此帶來的問題是, 內存中的數據頁會和磁盤上的數據頁內容不一致, 此時將內存中的這種數據頁稱爲 髒頁日誌
這裏的日誌指的是Redo Log(重作日誌), 這個日誌是循環寫入的.code
它記錄的是在某個數據頁上作了什麼修改, 這個日誌會攜帶一個LSN, 同時每一個數據頁上也會記錄一個LSN(日誌序列號).內存
這個日誌序列號(LSN)能夠用於數據頁是不是髒頁的判斷, 好比說 write pos對應的LSN比某個數據頁的LSN大, 則這個數據頁確定是乾淨頁, 同時當髒頁提早刷到磁盤時, 在應用Redo Log能夠識別是否刷過並跳過.ci
這裏有兩個關鍵位置點:it
當內存數據頁和磁盤數據頁內容不一致的時候, 將內存頁稱爲"髒頁".
內存數據頁寫入磁盤後, 兩邊內容一致, 此時稱爲"乾淨頁".
將內存數據頁寫入磁盤的這個操做叫作"刷髒頁"(flush).io
InnoDB是以緩衝池(Buffer Pool)來管理內存的, 緩衝池中的內存頁有3種狀態:
因爲InnoDB的策略一般是儘可能使用內存, 所以長時間運行的數據庫中的內存頁基本都是被使用的, 未被使用的內存頁不多.
刷髒頁的時機:
checkpoint 向前推動時, 須要將推動區間涉及的全部髒頁刷新到磁盤.
此時若是是乾淨頁, 則直接拿來複用.
若是是髒頁, 則須要先刷新到磁盤(直接寫入磁盤, 不用管Redo Log, 後續Redo Log刷髒頁時會判斷對應數據頁是否已刷新到磁盤), 使之成爲乾淨頁再拿來使用.
固然平時忙的時候也會盡可能刷髒頁.
此時須要將全部髒頁刷新到磁盤.
InnoDB須要控制髒頁比例來避免Redo Log寫滿以及單次淘汰過多髒頁過多的狀況.
這種狀況儘可能避免, 所以此時系統就不接受更新, 全部更新語句都會被堵住, 此時更新數爲0.
對於敏感業務來講, 這是不能接受的.
此時須要將 write pos 向前推動, 推動範圍內Redo Log涉及的全部髒頁都須要flush到磁盤中.
Redo Log設置太小或寫太慢的問題: 此時因爲Redo Log頻繁寫滿, 會致使頻繁觸發flush髒頁, 影響tps.
這種狀況實際上是常態.
當從磁盤讀取的數據頁在內存中沒有內存時, 就須要到緩衝池中申請一個內存頁, 這時候根據LRU(最久不使用)就須要淘汰掉一個內存頁來使用.
此時淘汰的是髒頁, 則須要將髒頁刷新到磁盤, 變成乾淨頁後才能複用.
注意, 這個過程 Write Pos 位置是不會向前推動的.
當一個查詢要淘汰的髒頁數太多, 會致使查詢的響應時間明顯變長.
InnoDB 控制刷髒頁的策略主要參考:
當髒頁比例接近或超過參數 innodb_max_dirty_pages_pct
時, 則會全力, 不然按照百分比.
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_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語句的響應時間.
這裏有一個案例:
測試在作壓力測試時, 剛開始 insert, update 很快, 可是一會就變慢且響應延遲很高.
↑ 出現這種狀況大部分是由於 Redo Log 設置過小引發的.
由於此時 Redo Log 寫滿後須要將 checkpoint 前推, 此時須要刷髒頁, 可能還會連坐(innodb_flush_neighbors=1
), 數據庫"抖"的頻率變高.
其實此時內存的髒頁比例可能還很低, 並無充分利用到大內存優點, 此時須要頻繁flush, 性能會變差.
同時, 若是Redo Log中存在change buffer, 一樣須要作相應的merge操做, 致使 change buffer 發揮不出做用.