天天看點

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

作者:JavaEdge

平時執行的更新語句,都是從磁盤上加載資料頁到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更新的緩存頁都刷盤。

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

在這種刷髒頁情況下,因為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語句的影響。