Mysql忽然變慢?可能是因為它在flush

2021-10-10 12:56:42 字數 3973 閱讀 3211

一條sql語句,正常執行的時候特別快,但是有時也不知道怎麼回事,它就會變得特別慢,並且這樣的場景很難復現,它不只隨機,而且持續時間還很短。看上去,這就像是資料庫「抖」了一下。

2 innodb刷髒頁的控制策略

mysql中的wal機制,innodb在處理更新語句的時候,只做了寫日誌這乙個磁碟操作。這個日誌叫作redo log(重做日誌)。

做下模擬的話,掌櫃記賬的賬本是資料檔案,記賬用的粉板是日誌檔案(redo log),掌櫃的記憶就是記憶體。

平時執行很快的更新操作,其實就是在寫記憶體和日誌,而mysql偶爾慢一下的那個瞬間,可能就是在刷髒頁(flush)。

當記憶體資料頁跟磁碟資料頁內容不一致的時候,稱這個記憶體頁為「髒頁」。記憶體資料寫入到磁碟後,記憶體和磁碟上的資料頁的內容就一致了,稱為「乾淨頁」。

第一種場景是,innodb的redo log寫滿了。這時候系統會停止所有更新操作,把checkpoint往前推進,redo log留出空間可以繼續寫。

checkpoint可不是隨便往前修改一下位置就可以的。比如圖2中,把checkpoint位置從cp推進到cp』,就需要將兩個點之間的日誌(淺綠色部分),對應的所有髒頁都flush到磁碟上。之後,圖中從write pos到cp』之間就是可以再寫入的redo log的區域。

第二種場景是,系統記憶體不足。當需要新的記憶體頁,而記憶體不夠用的時候,就要淘汰一些資料頁,空出記憶體給別的資料頁使用。如果淘汰的是「髒頁」,就要先將髒頁寫到磁碟。如果刷髒頁一定會寫盤,就保證了每個資料頁有兩種狀態:

第三種場景是,mysql認為系統「空閒」的時候。即使是用的比較多的時候,也要見縫插針地找時間,只要有機會就刷一點「髒頁」。

第四種場景是,mysql正常關閉的情況。這時候,mysql會把記憶體的髒頁都flush到磁碟上,這樣下次mysql啟動的時候,就可以直接從磁碟上讀資料,啟動速度會很快。

第三種情況是屬於mysql空閒時的操作,這時系統沒什麼壓力,而第四種場景是資料庫本來就要關閉了。這兩種情況下,不會太關注效能問題。

第一種是「redo log寫滿了,要flush髒頁」,這種情況是innodb要盡量避免的。因為出現這種情況的時候,整個系統就不能再接受更新了,所有的更新都必須堵住。如果從監控上看,這時候更新數會跌為0。

第二種是「記憶體不夠用了,要先將髒頁寫到磁碟」,這種情況其實是常態。innodb用緩衝池(buffer pool)管理記憶體,緩衝池中的記憶體頁有三種狀態:

innodb的策略是盡量使用記憶體,因此對於乙個長時間執行的庫來說,未被使用的頁面很少。

而當要讀入的資料頁沒有在記憶體的時候,就必須到緩衝池中申請乙個資料頁。這時候只能把最久不使用的資料頁從記憶體中淘汰掉:如果要淘汰的是乙個乾淨頁,就直接釋放出來復用;但如果是髒頁,就必須將髒頁先刷到磁碟,變成乾淨頁後才能復用。

所以,刷髒頁雖然是常態,但是出現以下這兩種情況,都是會明顯影響效能的:

乙個查詢要淘汰的髒頁個數太多,會導致查詢的響應時間明顯變長;

日誌寫滿,更新全部堵住,寫效能跌為0,這種情況對敏感業務來說,是不能接受的。

所以,innodb需要有控制髒頁比例的機制,來盡量避免上面的這兩種情況。

首先,要正確地告訴innodb所在主機的io能力,這樣innodb才能知道需要全力刷髒頁的時候,可以刷多快。

這就要用到innodb_io_capacity這個引數,表示innodb的磁碟能力。這個值建議設定成磁碟的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

這個問題可以這麼想,如果刷太慢,會出現什麼情況?

首先是記憶體髒頁太多,其次是redo log寫滿。所以,innodb的刷盤速度就是要參考這兩個因素:乙個是髒頁比例,乙個是redo log寫盤速度。innodb會根據這兩個因素先單獨算出兩個數字。

引數innodb_max_dirty_pages_pct是髒頁比例上限,預設值是75%。innodb會根據當前的髒頁比例(假設為m),算出乙個範圍在0到100之間的數字,計算這個數字的偽**類似這樣:

f1

(m)

innodb每次寫入的日誌都有乙個序號,當前寫入的序號跟checkpoint對應的序號之間的差值,設為n。innodb會根據這個n算出乙個範圍在0到100之間的數字,這個計算公式可以記為f2(n),n越大,算出來的值越大。

然後,根據上述算得的f1(m)和f2(n)兩個值,取其中較大的值記為r,之後引擎就可以按照innodb_io_capacity定義的能力乘以r%來控制刷髒頁的速度。

圖中的f1、f2就是上面我們通過髒頁比例和redo log寫入速度算出來的兩個值。

innodb會在後台刷髒頁,而刷髒頁的過程是要將記憶體頁寫入磁碟。所以,無論是查詢語句在需要記憶體的時候可能要求淘汰乙個髒頁,還是由於刷髒頁的邏輯會占用io資源並可能影響到了更新語句,都可能是造成從業務端感知到mysql「抖」了一下的原因。

要盡量避免這種情況,就要合理地設定innodb_io_capacity的值,並且平時要多關注髒頁比例,不要讓它經常接近75%

其中,髒頁比例是通過innodb_buffer_pool_pages_dirty除以innodb_buffer_pool_pages_total得到的,具體的命令參考下面的**:

select variable_value into

@afrom global_status where variable_name =

'innodb_buffer_pool_pages_dirty'

;select variable_value into

@bfrom global_status where variable_name =

'innodb_buffer_pool_pages_total'

;select@a/

@b;

乙個有趣的策略:

一旦乙個查詢請求需要在執行過程中先flush掉乙個髒頁時,這個查詢就可能要比平時慢了。而mysql中的乙個機制,可能讓你的查詢會更慢:在準備刷乙個髒頁的時候,如果這個資料頁旁邊的資料頁剛好是髒頁,就會把這個「鄰居」也帶著一起刷掉;而且這個把「鄰居」拖下水的邏輯還可以繼續蔓延,也就是對於每個鄰居資料頁,如果跟它相鄰的資料頁也還是髒頁的話,也會被放到一起刷。

在innodb中,innodb_flush_neighbors 引數就是用來控制這個行為的,值為1的時候會有上述的「連坐」機制,值為0時表示不找鄰居,自己刷自己的。

找「鄰居」這個優化在機械硬碟時代是很有意義的,可以減少很多隨機io。機械硬碟的隨機iops一般只有幾百,相同的邏輯操作減少隨機io就意味著系統效能的大幅度提公升。

而如果使用的是ssd這類iops比較高的裝置的話,就建議把innodb_flush_neighbors的值設定成0。因為這時候iops往往不是瓶頸,而「只刷自己」,就能更快地執行完必要的刷髒頁操作,減少sql語句響應時間。

在mysql 8.0中,innodb_flush_neighbors引數的預設值已經是0了。

差異可能是重要的

3位可儲存8個值。n位可儲存2 n值。因為乙個位元組的8位,乙個位元組可以存放2 8 256 的值。變數的大小對大量的資訊可以儲存 這是更大的變數可以容納更多的限制。我們將進一步解決這個問題的時候,我們進入不同型別的變數。第二,電腦有乙個有限的可用記憶體。每一次我們宣告乙個變數,那游離的記憶是只要用...

你可能是自由的

序 一直都愛吃甜,甜到微微的澀。一直都想寫作,寫到天昏地暗。一直追逐自由,飛到天涯海角。孤獨的牧羊人在廣廖的草原牧著他的羊兒們,一生孤獨的他在3歲那年,父母已離開人世,剩下相依為命的奶奶也在10歲時離他而去,留下的只有兩頭羊兒。那一年,他哭的是那麼傷心,周圍嬉皮笑臉的人帶著乙個悲傷的面具,假惺惺的來...

這可能是不可取的!

雖然消極期望功能的一部分,我們給它乙個整數而不是。然而,由於分數具有建構函式的願意接受乙個整數,編譯器將使用此建構函式隱式轉換為整數6轉化為分數的物件,然後將其複製到makenegative 初始化引數f.因此,上面的程式列印 6 1 這種隱式轉換為各種初始化 直接 統一和複製 用於隱式會話的建構函...