系列文章
六、五分鍾,讓你明白MySQL是怎么選擇索引《死磕MySQL系列 六》
七、字符串可以這樣加索引,你知嗎?《死磕MySQL系列 七》
項目中將MySQL的報錯、異常、執行時間長的都打到了釘釘群中,這樣有利於平時及時處理。今天要聊的是無法復現的慢查詢。
一、為什會出現無法復現的“慢”SQL
在一生摯友redo log、binlog《死磕MySQL系列 二》中詳細的說明了redo log、binlog。此時你知道了在更新時當事務提交后,並非直接修改數據庫的數據,而是先更新內存並在 redo log中記錄相關的操作。
總歸是要把內存的數據刷入磁盤中,也可以稱之為刷臟頁(flush)。
什么是臟頁、干凈頁
大多數資料都提及到臟頁,那么臟頁到底是什么呢?臟頁時內存數據頁的數據跟磁盤數據不一致時,就稱這個內存頁為臟頁。
當內存頁寫入磁盤后,內存和磁盤的數據頁就一致了,此時稱這個內存頁為干凈頁。
什么時候臟頁會變為干凈頁
第一種
Innodb的redo log寫滿了,也就是下圖的write pos 追上了check point了,此時系統所有的更新操作都會停止。
直至check point推進了,對應的臟頁都flush到磁盤了,redo log才可以繼續寫。
一般情況下這個redo log日志在開發前期根據innodb_log_file_size參數設置好后就不會出現redo log寫滿的情況。
第二種
內存不足導致,更新一條語句會先更新內存再更新到redo log,若內存不足就無法申請新的內存就需要淘汰一些數據頁。就需要把臟頁flush到磁盤。
有沒有想過既然更新操作給內存和redo log都存了一份,那么能不能直接把內存頁淘汰掉,再有請求時從磁盤讀入數據頁再把redo log拿出來應用不行嗎?
內存滿時不刷臟頁而直接淘汰掉,那下次請求磁盤中的干凈頁到內存時,還需要額外的判斷redo log中是否有對該頁的修改,有的話還需要對它應用redo log。這個臟頁始終都是要刷盤的,但現在缺額外多了應用redo log的操作。所以不能直接淘汰內存,而是內存滿時直接flush。
另外,redo log是循環寫的,若想應用redo log那么redo log就要一直存在,不能刪除。違背了系統設計。
第三種
MySQL在系統低峰期時進行刷臟頁
第四種
MySQL正常關閉時會把內存的臟頁都刷到磁盤中,重啟后從磁盤直接讀數據,啟動速度會很快。
結論
到這里你就應該明白,莫名其妙的慢SQL就是因為flush造成的,那么這四種情況都是怎么影響MySQL的呢?
二、四種flush對性能的影響
第三、四種情況不會因為flush而導致MySQL執行慢,一個是系統空閑時段,另一個是數據庫本來就要關閉了。
redo log寫滿了,需要flush臟頁
這種情況在第二期文章中就已經給了方案,redo log一旦寫滿整個系統就不再接受更新操作了, 所有的更新操作都得停滯,直到check point推進了。
擴展
在MySQL中提供了innodb_log_file_size參數來優化redo log日志。
對於innodb_log_file_size的設置也是有一些計算規則的,下面將為你介紹。
若innodb_log_file_size設置太小,將導致redo log文件頻繁切換,頻繁的觸發數據庫的檢查點(check point),導致記錄更新到數據文件的次數增加,從而影響IO性能。
同樣,如果有一個大的事務,並且所有 redo log日志都已寫滿,但是還沒有完成,將導致日志無法切換,從而導致 MySQL直接堵死。
innodb_log_file_size設置太大,雖然極大地提高了 IO性能,但是在 MySQL重啟或宕機時,恢復時間會因為 redo log文件過大而延長。而這種恢復時間通常是無法控制的。
如何合理的設置innodb_log_file_size?
用一個腳本定時執行,記錄對應時間的sequenumber再取平均值,計算出的誤差將減至最小。sequenumber是當每個 binlog生成時,該值從1開始,然后遞增,每增加一個事務, sequenumber就加上1。
系統內存不足,要刷臟頁
Innodb中管理內存的是buffer pool,內存頁在上文可得知存在三種狀態,未使用的、使用了是干凈頁、使用了是臟頁。
對於一個長時間運行的庫來說,未被使用的頁非常少,當內存不足時,就只能把最久不使用的數據頁從內存中淘汰掉。
若淘汰的是一個干凈頁,就直接釋放使用,但如果是臟頁就必須先把臟頁刷盤,變為干凈頁進行復用。
查詢的數據沒有在內存中,就需要把數據從磁盤中讀入數據,若讀的數據太多就需要淘汰多個臟頁,會導致查詢時間邊長。
redo log日志寫滿,所有的更新系統都不執行,對於大多數業務來說都不能接受。
為了防止這種情況的發生就需要控制刷臟頁的頻率。
三、如何設置刷臟頁的速度
刷臟頁到磁盤的快慢必定跟系統的IO能力有關,在MySQL中innodb_io_capacity是控制刷臟頁的速度。
在從緩沖區刷新臟頁時(check point),每秒刷新臟頁的數量就等於innodb_io_capacity的值。
這個值可以設置成磁盤的IOPS,可以使用fio工具來測試,具體使用這里就不聊了。
刷臟頁的速度也要根據臟頁比例、redo log寫盤速度來決定。
參數innodb_max_dirty_pages_pct是臟頁比例上限,在MySQL8.0這個比例默認為90%,MySQL5.6還是75%。
一般情況下對於innodb_io_capacity的值設置為臟頁比例上限與寫redo log日志時的日志序號減去checkpoint的值,倆個值取最大的即可。
臟頁比例的計算公式是Innodb_buffer_pool_pages_dirty/Innodb_buffer_pool_pages_total
,具體執行命令為
mysql> select VARIABLE_VALUE into @a from global_status where VARIABLE_NAME = 'Innodb_buffer_pool_pages_dirty';
select VARIABLE_VALUE into @b from global_status where VARIABLE_NAME = 'Innodb_buffer_pool_pages_total';
select @a/@b;
在這個SQL語句中可以看到使用的是global_status這張表在performance_schema這個庫里邊。執行命令前需要執行use performance_schema。
當你的MySQL寫入速度很慢,TPS很低,IO壓力不大時需要排查的地方
出現這個問題時就考慮下一下innodb_io_capacity這個參數值設置是否合理。
在1核2G的服務器默認值是200,在公司服務器上看是2000,也是跟服務器配置有關系的。
四、有趣參數
在MySQL8.0中參數innodb_flush_neighbors默認值為0。
當一個查詢需要在執行過程中先flush掉一個臟頁時,如果這個數據頁旁邊的數據頁剛好是臟頁,就會把這個數據頁一同刷掉,而這個連帶的邏輯會持續下去。會使SQL的查詢變的更慢。
“堅持學習、堅持寫作、堅持分享是咔咔從業以來所秉持的信念。願文章在偌大的互聯網上能給你帶來一點幫助,我是咔咔,下期見。
”