MySQL刷脏页
MySQL刷臟頁
InnoDB 在處理更新語句的時候,只做了寫日志這一個磁盤操作。這個日志叫作 redo log(重做日志),在更新內(nèi)存寫完 redo log 后,就返回給客戶端,本次更新成功。
內(nèi)存里的數(shù)據(jù)寫入磁盤的過程,術(shù)語就是 flush。
當(dāng)內(nèi)存數(shù)據(jù)頁跟磁盤數(shù)據(jù)頁內(nèi)容不一致的時候,我們稱這個內(nèi)存頁為“臟頁”。內(nèi)存數(shù)據(jù)寫入到磁盤后,內(nèi)存和磁盤上的數(shù)據(jù)頁的內(nèi)容就一致了,稱為“干凈頁” 。
什么情況會引發(fā)數(shù)據(jù)庫的 flush 過程呢?
1、InnoDB 的 redo log 寫滿了。這時候系統(tǒng)會停止所有更新操作,把 checkpoint 往前推進(jìn),redo log 留出空間可以繼續(xù)寫。
2、系統(tǒng)內(nèi)存不足。當(dāng)需要新的內(nèi)存頁,而內(nèi)存不夠用的時候,就要淘汰一些數(shù)據(jù)頁,空出內(nèi)存給別的數(shù)據(jù)頁使用。如果淘汰的是“臟頁”,就要先將臟頁寫到磁盤。
3、MySQL認(rèn)為系統(tǒng)“空閑”的時候。
4、MySQL 正常關(guān)閉的情況。這時候,MySQL 會把內(nèi)存的臟頁都 flush 到磁盤上,這樣下次 MySQL 啟動的時候,就可以直接從磁盤上讀數(shù)據(jù),啟動速度會很快。
InnoDB 刷臟頁的控制策略
-- 當(dāng)刷新臟數(shù)據(jù)時,控制MySQL每秒執(zhí)行的寫IO量 select @@innodb_io_capacityinnodb_io_capacity值建議設(shè)置成磁盤的 IOPS。磁盤的 IOPS 可以通過 fio 這個工具來測試
fio -filename=$filename -direct=1 -iodepth 1 -thread -rw=randrw -ioengine=psync -bs=16k -size=500M -numjobs=10 -runtime=10 -group_reporting -name=mytest控制刷臟頁的速度,會參考哪些因素呢?
InnoDB 的刷盤速度要參考這兩個因素:一個是臟頁比例,一個是redo log寫盤速度。
參數(shù) innodb_max_dirty_pages_pct 是臟頁比例上限,默認(rèn)值是 75%。InnoDB 會根據(jù)當(dāng)前的臟頁比例(假設(shè)為 M),算出一個范圍在 0 到 100 之間的數(shù)字,計(jì)算這個數(shù)字的偽代碼類似這樣:
F1(M) {if M>=innodb_max_dirty_pages_pct thenreturn 100;return 100*M/innodb_max_dirty_pages_pct; }InnoDB 每次寫入的日志都有一個序號,當(dāng)前寫入的序號跟 checkpoint 對應(yīng)的序號之間的差值,我們假設(shè)為 N。InnoDB 會根據(jù)這個 N 算出一個范圍在 0 到 100 之間的數(shù)字,這個計(jì)算公式可以記為 F2(N)。F2(N) 算法比較復(fù)雜,你只要知道 N 越大,算出來的值越大就好了。
然后,根據(jù)上述算得的 F1(M) 和 F2(N) 兩個值,取其中較大的值記為 R,之后引擎就可以按照 innodb_io_capacity 定義的能力乘以 R% 來控制刷臟頁的速度。
InnoDB 會在后臺刷臟頁,而刷臟頁的過程是要將內(nèi)存頁寫入磁盤。所以,無論是你的查詢語句在需要內(nèi)存的時候可能要求淘汰一個臟頁,還是由于刷臟頁的邏輯會占用 IO 資源并可能影響到了你的更新語句,都可能是造成你從業(yè)務(wù)端感知到 MySQL“抖”了一下的原因。
要盡量避免這種情況,你就要合理地設(shè)置 innodb_io_capacity 的值,并且 平時要多關(guān)注臟頁比例,不要讓它經(jīng)常接近 75% 。
其中,臟頁比例是通過Innodb_buffer_pool_pages_dirty/Innodb_buffer_pool_pages_total得到的,具體的命令參考下面的代碼:
use performance_schema; select VARIABLE_VALUE from global_status where VARIABLE_NAME = 'Innodb_buffer_pool_pages_dirty'; select VARIABLE_VALUE from global_status where VARIABLE_NAME = 'Innodb_buffer_pool_pages_total'; select (select VARIABLE_VALUE from global_status where VARIABLE_NAME = 'Innodb_buffer_pool_pages_dirty') / (select VARIABLE_VALUE from global_status where VARIABLE_NAME = 'Innodb_buffer_pool_pages_total');一旦一個查詢請求需要在執(zhí)行過程中先 flush 掉一個臟頁時,這個查詢就可能要比平時慢了。而 MySQL 中的一個機(jī)制,可能讓你的查詢會更慢:在準(zhǔn)備刷一個臟頁的時候,如果這個數(shù)據(jù)頁旁邊的數(shù)據(jù)頁剛好是臟頁,就會把這個“鄰居”也帶著一起刷掉;而且這個把“鄰居”拖下水的邏輯還可以繼續(xù)蔓延,也就是對于每個鄰居數(shù)據(jù)頁,如果跟它相鄰的數(shù)據(jù)頁也還是臟頁的話,也會被放到一起刷。
在 InnoDB 中,innodb_flush_neighbors 參數(shù)就是用來控制這個行為的,值為 1 的時候會有上述的“連坐”機(jī)制,值為 0 時表示不找鄰居,自己刷自己的。
找“鄰居”這個優(yōu)化在機(jī)械硬盤時代是很有意義的,可以減少很多隨機(jī) IO。機(jī)械硬盤的隨機(jī) IOPS 一般只有幾百,相同的邏輯操作減少隨機(jī)IO就意味著系統(tǒng)性能的大幅度提升。
而如果使用的是 SSD 這類 IOPS 比較高的設(shè)備的話,我就建議你把 innodb_flush_neighbors 的值設(shè)置成 0。因?yàn)檫@時候 IOPS 往往不是瓶頸,而“只刷自己”,就能更快地執(zhí)行完必要的刷臟頁操作,減少 SQL 語句響應(yīng)時間。
在 MySQL 8.0 中,innodb_flush_neighbors 參數(shù)的默認(rèn)值已經(jīng)是 0 了。
總結(jié)
- 上一篇: mysql脏写_图解脏写、脏读、不可重复
- 下一篇: ssi oa mysql_SSI集成My