為什麼我的MySQL會「抖」一下

2022-09-17 21:15:23 字數 4801 閱讀 5627

平時的工作中,不知道你有沒有遇到過這樣的場景,一條sql語句,正常執行的時候特別快,但是有時也不知道怎麼回事,它就會變得特別慢,並且這樣的場景很難復現,它不只隨機,而且持續時間還很短。

看上去,這就像是資料庫「抖」了一下。今天,我們就一起來看一看這是什麼原因。

我為你介紹了wal機制。現在你知道了,innodb在處理更新語句的時候,只做了寫日誌這乙個磁碟操作。這個日誌叫作redo log(重做日誌),也就是《孔乙己》裡咸亨酒店掌櫃用來記賬的粉板,在更新記憶體寫完redo log後,就返回給客戶端,本次更新成功。

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

掌櫃總要找時間把賬本更新一下,這對應的就是把記憶體裡的資料寫入磁碟的過程,術語就是flush。在這個flush操作執行之前,孔乙己的賒賬總額,其實跟掌櫃手中賬本裡面的記錄是不一致的。因為孔乙己今天的賒賬金額還只在粉板上,而賬本裡的記錄是老的,還沒把今天的賒賬算進去。

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

不論是髒頁還是乾淨頁,都在記憶體中。在這個例子裡,記憶體對應的就是掌櫃的記憶。

接下來,我們用乙個示意圖來展示一下「孔乙己賒賬」的整個操作過程。假設原來孔乙己欠賬10文,這次又要賒9文。

回到文章開頭的問題,你不難想象,平時執行很快的更新操作,其實就是在寫記憶體和日誌,而mysql偶爾「抖」一下的那個瞬間,可能就是在刷髒頁(flush)。

那麼,什麼情況會引發資料庫的flush過程呢?

我們還是繼續用咸亨酒店掌櫃的這個例子,想一想:掌櫃在什麼情況下會把粉板上的賒賬記錄改到賬本上?

這個場景,對應的就是innodb的redo log寫滿了。這時候系統會停止所有更新操作,把checkpoint往前推進,redo log留出空間可以繼續寫。我在第二講畫了乙個redo log的示意圖,這裡我改成環形,便於大家理解。

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

這種場景,對應的就是mysql認為系統「空閒」的時候。當然,mysql「這家酒店」的生意好起來可是會很快就能把粉板記滿的,所以「掌櫃」要合理地安排時間,即使是「生意好」的時候,也要見縫插針地找時間,只要有機會就刷一點「髒頁」。

接下來,你可以分析一下上面四種場景對效能的影響。

其中,第三種情況是屬於mysql空閒時的操作,這時系統沒什麼壓力,而第四種場景是資料庫本來就要關閉了。這兩種情況下,你不會太關注「效能」問題。所以這裡,我們主要來分析一下前兩種場景下的效能問題。

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

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

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

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

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

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

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

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

接下來,我就來和你說說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

其實,因為沒能正確地設定innodb_io_capacity引數,而導致的效能問題也比比皆是。之前,就曾有其他公司的開發負責人找我看乙個庫的效能問題,說mysql的寫入速度很慢,tps很低,但是資料庫主機的io壓力並不大。經過一番排查,發現罪魁禍首就是這個引數的設定出了問題。

他的主機磁碟用的是ssd,但是innodb_io_capacity的值設定的是300。於是,innodb認為這個系統的能力就這麼差,所以刷髒頁刷得特別慢,甚至比髒頁生成的速度還慢,這樣就造成了髒頁累積,影響了查詢和更新效能。

雖然我們現在已經定義了「全力刷髒頁」的行為,但平時總不能一直是全力刷吧?畢竟磁碟能力不能只用來刷髒頁,還需要服務使用者請求。所以接下來,我們就一起看看innodb怎麼控制引擎按照「全力」的百分比來刷髒頁。

根據我前面提到的知識點,試想一下,如果你來設計策略控制刷髒頁的速度,會參考哪些因素呢?

這個問題可以這麼想,如果刷太慢,會出現什麼情況?首先是記憶體髒頁太多,其次是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)。f2(n)演算法比較複雜,你只要知道n越大,算出來的值越大就好了。

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

上述的計算流程比較抽象,不容易理解,所以我畫了乙個簡單的流程圖。圖中的f1、f2就是上面我們通過髒頁比例和redo log寫入速度算出來的兩個值。

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

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

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

mysql>

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了。

資料庫學習之為什麼mysql會出現」抖「一下的情況

1 抖 一下情況,是什麼樣子 一條sql語句,正常執行的時候特別塊,但是有時候不知道為什麼會變得特別慢 2 髒頁 和 乾淨頁 在將redo log的理論理解過程中,我們把redo log這個位置,理解為黑板。把磁碟這個位置理解為賬本。先寫黑板,在寫賬本,先後過程,所以肯定存在資料的不一致。因此,髒頁...

MySQL所謂的髒頁和「抖」一下是什麼聯絡?

在我們平時經常用到的sql更新語句,之前是認為只要sql執行,當前sql的操作會立馬執行到伺服器磁碟上並返回,但是後來我才知道,事實並非如此,在了解事實之前,首先可能需要先了解什麼是redo log,什麼是buffer pool,什麼是changebuffer以及資料頁。首先,我們用一種比喻的手法,...

面試的時候我為什麼會緊張

今天上午經理過我們的專案,好多人,而且還是站起來做自我介紹,專案介紹,我怕了。我沒有回答好,自我介紹是早就寫好了,自己有時候拿來背背,我覺得這是很簡單的問題,可是,在我從位置上站起來的那一刻,我怕了。磕磕絆絆的說了好一會,最後經理說 前面還可以,挺好的,就是後面太緊張了,心理素質有點差,回去好好練練...