線上MySQL頻繁抖動的性能優化實戰

平時執行的更新語句,都是從磁盤上加載數據頁到DB內存的緩存頁,接着就直接更新內存裏的緩存頁,同時還更新對應的redo log寫入一個buffer中。

既然更新了BP裏的緩存頁,緩存頁就會變成髒頁,就得有時機把那髒頁給刷到磁盤文件,髒頁刷盤機制,是維護了一個LRU鏈表。

後續若要加載磁盤文件的數據頁到BP,但此時並無空閒緩存頁,就得將部分髒緩存頁刷入到磁盤,此時就會根據LRU刷盤。

萬一你執行查詢,需查大量數據到緩存頁,可能導致內存裏大量髒頁需淘汰刷盤,才能騰出足夠內存執行這條查詢SQL。這時可能發現突然莫名線上DB執行某查詢SQL就突然性能出現抖動,平時只要幾十ms查詢,這次一下子要幾s,畢竟你要等待大量髒頁flush磁盤,然後語句才能執行。

還有一種髒頁刷盤時機,redo log buffer裏的redo log本身也會隨各種條件刷入磁盤上的日誌文件,比如:

  • redo log buffer裏的數據超過容量的一定比例

  • 或事務提交

都會強制buffer裏的redo log刷入磁盤上的日誌文件。磁盤上有多個日誌文件,他會依次不停寫,若寫滿所有日誌文件,此時會重新回到第一個日誌文件再次寫入,這些日誌文件是不停的循環寫入的,所以其實在日誌文件都被寫滿的情況下,也會觸發一次髒頁的刷新。因爲假設你的第一個日誌文件的一些redo log對應內存裏的緩存頁的數據都沒被刷新到磁盤數據頁,那一旦你把第一個日誌文件裏的這部分redo log覆蓋寫了別的日誌,若此時DB崩潰, 有些你之前更新過的數據不就徹底丟了?所以一旦你將寫滿所有日誌文件,此時重新從第一個日誌文件開始寫時,會判斷,若要是你第一個日誌文件裏的一些redo log對應之前更新過的緩存頁,至今都沒刷盤,則此時得將那些馬上要被覆蓋的redo log更新的緩存頁都刷盤。

在這種刷髒頁情況下,因爲redo log所有日誌文件都寫滿,會導致DB 夯死,無法處理任何更新請求,因爲執行任何一個更新請求都必須要寫redo log!此時就得將一些髒頁刷盤,才能繼續執行更新語句,把更新語句的redo log從第一個日誌文件開始覆蓋寫。

所以此時假設你在執行大量更新語句,可能突然發現線上DB莫名很多更新語句短時間內性能都抖動了,可能很多更新語句平時就幾ms執行完,這次要等待1s才能執行完。

解決方案

必須要等待第一個日誌文件裏部分redo log對應的髒頁都刷盤,才能繼續執行更新語句,這勢必導致更新語句的性能很差。

綜上,導致線上DB的查詢和更新語句莫名出現性能抖動,很可能就是上述兩種情況導致的執行語句時大量髒緩存頁刷入磁盤,你要等待他們刷完磁盤才能繼續執行。

在DB裏執行查詢或更新語句時,可能SQL語句性能會莫名抖動,根本原因:

  • BP緩存頁都滿了,此時執行一個SQL查詢大量數據,一下將大量緩存頁flush到磁盤,刷磁盤太慢,導致你的查詢語句執行就很慢

    因爲你必須等大量緩存頁都flush到磁盤,才能執行查詢從磁盤將你所需數據頁加載到BP緩存頁

  • 執行更新語句時,redo log在磁盤上的所有文件都寫滿了

    此時需要回到第一個redo log文件覆蓋寫,覆蓋寫時可能涉及到第一個redo log文件裏有很多redo log日誌對應的更新操作改動了緩存頁,那些緩存頁還沒刷盤,就必須把它們刷盤了,才能執行更新語句,而你這一等待,必然會導致更新執行的很慢

所以上述兩個場景導致的大量緩存頁flush到磁盤,就會導致莫名SQL性能抖動。

MySQL調優,降低緩存頁刷盤對性能的影響

要達此目的,關鍵如下:

減少緩存頁刷盤頻率

很難!因爲平時你的緩存頁就是正常的在被使用,終究會被填滿。

一旦填滿,必然你執行下個SQL就會導致一批緩存頁刷盤,這很難控制,除非你給你的數據庫採用大內存機器,給BP分配的內存空間大些,則其緩存頁填滿的速率低些,刷盤頻率也就較低。

提升緩存頁刷盤速度

所以關鍵就是如何儘量提升緩存頁的刷盤速度。

假設現在要執行一個查詢,需等待flush一批緩存頁,接着才能加載查詢出來的數據到緩存頁。

那麼若flush那批緩存頁到磁盤需1s,然後查詢SQL本身執行200ms,此時你這條SQL執行完畢總時間就得1.2s。

但若將那批緩存頁刷盤的時間優化到100ms,該SQL總執行時間就只需300ms,性能提升很多。所以關鍵之一就是儘可能減少緩存頁刷盤的時間開銷到min。

爲此:

  • 推薦採用SSD,因其隨機I/O性能非常高。而flush緩存頁到磁盤,就是典型隨機I/O,得在磁盤上找到各緩存頁所在的隨機位置,把數據寫入磁盤

  • 還得設置DB的innodb_io_capacity參數,告訴DB採用多大的I/O速率刷盤

假設你SSD能承載的隨機I/O 600次/s,結果你把數據庫的innodb_io_capacity就設爲300,即刷盤時隨機I/O最多執行300次/s,那你速度就還是很慢啊,根本沒壓榨完SSD隨機I/O性能

所以推薦對DB部署機器的SSD能承載的最大隨機I/O速率做個測試,fio工具常用來測試磁盤最大隨機I/O速率。這樣就知道他可執行多少次隨機I/O / s,然後將該數值設置給DB的innodb_io_capacity。

但實際flush時,其實會按innodb_io_capacity乘以一個百分比來進行刷盤,就是髒頁比

例,innodb_max_dirty_pages_pct參數控制,默認75%,一般不動它,另外該比例也可能會變化,該比例同時會參考你的redo log日誌來計算的。關於這比例,這裏的優化其實不用關注,核心就是把innodb_io_capacity設爲SSD的IOPS,即隨機I/O速率。

innodb_flush_neighbors

在緩存頁刷盤時,可能會控制把緩存頁臨近的其他緩存頁也刷盤。

但這有時會導致flush緩存頁太多。實際上若你用SSD,並無必要讓他同時刷鄰近緩存頁,可將innodb_flush_neighbors設爲0,禁止刷臨近緩存頁,這樣就把每次刷新的緩存頁數量降低到min。

所以針對本文案例,即MySQL性能隨機抖動問題,關鍵就是:

  • 將innodb_io_capacity設爲SSD 固態硬盤的IOPS,讓他刷緩存頁儘量快

  • 同時設置innodb_flush_neighbors爲0,讓他每次別刷臨近緩存頁,減少要刷緩存頁的數量

這樣就可以把刷緩存頁的性能提升到最高,也儘可能降低每次刷緩存頁對執行SQL語句的影響。

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