參考:
https://blog.csdn.net/fouy_yun/article/details/103056296
https://blog.csdn.net/qq_41086588/article/details/104372807
https://www.jianshu.com/p/6ffb01aa7717
MySQL抖動的原因
我們在使用MySQL實現業務處理的時候,更多的關注可能在SQL本身上面是不是最優的。今天我們從一個很小的點去看一下MySQL的實現原理,那就是MySQL刷臟頁相關的問題。
刷臟頁
在MySQL中,如果內存數據頁(buffer pool)和磁盤數據不一致時,這個內存數據頁我們認為是“臟頁”;當內存數據頁 flush 到磁盤之后,內存數據頁和磁盤數據一致時,此時這個內存數據頁就稱為“干凈頁”。(內存中的數據和磁盤一致就可以認為是干凈頁)刷臟頁的過程可以參見如下所示:
從上圖可以看出,更新數據時,內存更新完成+redo log 寫完之后就算成功了。但是此時內存中是存在臟頁的,也就是數據還沒有同步到磁盤上。第二個過程就是將內存中的數據同步到磁盤上面。
觸發刷臟頁的條件
-
redo log 寫滿了。因為 redo log 是一塊固定大小的磁盤空間,當redo log 寫滿之后,會強制觸發磁盤的臟頁 flush 。
-
系統內存不足。因為數據的更新會更新 內存頁+redo log,此時有一個空間不足都會觸發臟頁 flush。
-
系統負載不高時觸發。也就是系統空閑時可以 flush 一些臟頁,以應對后續可能出現的高負載。
-
MySQL正常關閉。數據庫正常關閉時,此時所有的數據肯定需要寫入磁盤持久化。
從上面的幾個條件我們可以看出,對於MySQL 中的更新數據,主要有以下 2 種狀態:1、內存中有(可能是臟頁或者干凈頁)數據,此時更新操作只需要更新內存 + 寫入 redo log;2、內存中無對應的數據,此時就需要從磁盤中讀取對應的數據,然后更新內存 + 寫入 redo log。
對於上面可能出現MySQL抖動的條件,我們主要看前面 2 種。
-
對於第一種,當 redo log 寫滿的時候,MySQL就會拒絕服務,此時不會接受任何更新操作。因此這種情況是 MySQL 所需要避免的。
-
對於第二種,就是內存不夠用了,這種情況出現的概率還是比較大的。InnoDB 使用 Buffer Pool 管理內存,對於 Buffer Pool 中的內存主要有以下幾種狀態:未被使用;已使用且是干凈頁;已使用且是臟頁。
因此,當內存不足時,如果要申請一個內存頁時,此時就需要淘汰一個最久不使用的內存頁:如果是“干凈頁”,則直接釋放出來使用;如果是“臟頁”則首先需要 flush 然后再釋放使用。
總結以上,MySQL發生抖動的原因主要有以下:
-
內存不足時,所需要淘汰的內存頁(臟頁)太多;
-
redo log 寫滿時,這種可能出現瞬間的拒絕服務。
InnoDB 的 flush 臟頁策略
innodb_io_capacity
參數,它會告訴 InnoDB 你的磁盤讀寫能力是多少。這個參數建議設置成機器的 IOPS ,可以通過 fio 工具來測試獲得。如下所示:
$ fio -filename=data.mp4 -direct=1 -iodepth 1 -thread -rw=randrw -ioengine=psync -bs=16k -size=500M -numjobs=10 -runtime=10 -group_reporting -name=mytest
測試完成之后如下圖所示:
如果 innodb_io_capacity 設置的過小,比如比正常刷臟頁的速度還慢,則肯定影響正常的IO 讀寫。
-
innodb_max_dirty_pages_pct
參數: 臟頁的比例,默認是 75%。 -
innodb_flush_neighbors
參數:刷臟頁的時候,如果相鄰的內存頁也是臟頁的話,也會flush。這個參數在機械硬盤時代,可能會減少隨機IO 的開銷。使用 SSD 的話建議設置成 0 ,即關閉。
參考:《極客時間:MySQL實戰》、《高性能MySQL》
鏈接:http://moguhu.com/article/detail?articleId=122
線上Mysql為什么會出現性能抖動和相應的解決辦法
1.基礎知識
1.Innodb處理更新語句的流程
(1).首先Server層的執行器調用InnoDB數據接口,請求數據 --Server層
(2).InnoDB會先判斷該數據**是否存在於內存**當中,如果在,走(3),如果不在則走(4) --InnoDB引擎層
(3).將數據返回給Server層 --InnoDB引擎層
(4).加載數據到內存,並將數據返回到Server層 --InnoDB引擎層
(5).Server層的執行器對數據進行修改並調用引擎的寫入接口 --Server層
(6).InnoDB將數據更新應用到內存 --InnoDB引擎層
(7).寫入redo log日志(事務處於prepare狀態) --InnoDB引擎層
(8).寫入binlog日志(Server層維護的日志,用於歸檔和主備,數據恢復) --Server層
(9).提交事務,事務處於commit狀態 --InnoDB引擎層
從上述的數據更新過程當中,我們可以看到InnoDB在做數據更新時,只進行了一次磁盤操作,即寫redo log日志。而由於redo log日志是以append-only的方式寫入的,避免了隨機寫,所以效率很高。
2.簡單介紹redo log和binlog
redo log是由InnoDB引擎維護的日志,可以保證數據庫的crash-safe。這是由於redo log記錄了數據頁的物理變化,可以在數據庫發生異常關機或者在重啟時,通過重放redo log,保證數據庫數據的正確性。
binlog日志則是由Server層維護的日志,可供所用引擎使用。binlog主要作為邏輯日志用於歸檔,有mixed,statement,row三種格式。可用作故障恢復,主被復制,主從模型等等高可用架構。
binlog 和 redo log的不同之處在於:
- binlog是由Server層維護的,可供所有引擎共同使用。而redo log則是由InnoDB引擎維護的,以插件形式提供給Mysql使用的。
- binlog主要作為邏輯日志用作歸檔,而沒有crash-safe功能。
- binlog是追加寫的,並沒有大小的限制,寫滿一個文件之后可生成新的日志文件繼續記錄,即可生成多個binlog日志文件。而redo log是一組固定大小的日志文件,當日志文件寫滿之后會從頭循環寫。
redo log通過減少隨機寫入提高了Mysql的性能。
3.為什么會出現性能抖動?
有什么我們在線上會發現線上數據庫操作會發生性能上的抖動,查看數據庫執行語句,發現也正常使用了索引。並且這種情況往往是隨機的,很難復現。出現這種情況,我們往往可以考慮,是否是由於Mysql刷新臟頁的動作導致的。
由於在已經回顧了redo log的知識,我們可以探索有那些情況會觸發刷新臟頁的動作。
- redo log寫滿了。redo log寫滿了,也就意味着write_pos追上了checkpoint。這個時候,會造成阻塞所有操作,並進行刷新臟頁的動作。由於會造成線上業務的暫時停擺,所以要盡量避免這種情況。
- 內存空間用完了。我們知道,InnoDB維護了InnoDB Buffer Pool來將數據以數據頁的形式(每個數據頁16KB)加載到內存當中,以提供Mysql的效率。但是如果內存空間用完了,也會造成Mysql刷新臟頁的動作。
- 在Mysql系統空閑時,會通過后台線程刷新臟頁。
- 在Mysql正常關閉的時候,會將內存當中的所有臟頁刷到磁盤當中。
在這里由於第三,第四種情況屬於正常情況,我們在這里不做討論。而第一種情況,我們可以根據系統的性能指標,通過適當的增大redo log的大小來進行改善。因此,在這里我們主要討論第二中情況。
什么是臟頁?
臟頁,在Mysql當中指的是內存當中與磁盤當中數據不一直的數據頁(注意這里描述的是內存當中的數據頁)。
那么在InnoDB的緩存池當中有三種狀態的數據頁:即臟頁,干凈頁,未使用頁。
而當要讀入的數據沒有在內存當中的時候,就會去InnoDB緩沖池當中申請一個數據頁,如果內存不夠,InnoDB緩沖池就只能將最不經常使用的數據頁淘汰。如果淘汰的是干凈頁,則可以直接使用。而如果淘汰的是臟頁,就必須先進行flush操作,使臟頁變成干凈頁才能使用。因此,如果一個查詢語句需要淘汰大量的臟頁,就會造成Mysql性能抖動的現象。
如何控制InnoDB刷新臟頁的策略?
首先要理解一個參數:innodb_io_capacity。這個參數是告訴innodb,當系統需要全力刷新臟頁時,可以刷的多快。而這個參數一般建議設置為IOPS(IOPS可以通過fio工具進行磁盤測試得到)。
如果對於一個固態硬盤的innodb_io_capacity設置的過小,就可能會導致InnoDB臟頁的生成速度遠大於臟頁的刷新速度,但磁盤的io負載並不高的情況。但是,我們InnoDB又不會完全按照innodb_io_capacity所設置的值一直全力刷新臟頁。而是會根據一定規則,計算出一個平均值R,以innodb_io_capacity * R% 的能力刷新臟頁,這是比較合理的。這是因為InnodbDB有很多的磁盤操作,比如從磁盤當中查詢數據,不能把所有的io能力都放在了刷新臟頁上。
因此,如果線上數據庫發生了性能抖動問題,而通過慢sql的排查,發現sql可以正常的使用索引,我們可以懷疑是因為Mysql的臟頁刷新機制導致的。具體的解決方案,我們可以通過適當的增大redo log的大小,並且合理的設置innodb_io_capacity 的參數值來改善。
二 有可能是Mysql有后台線程進行備份
對於Mysql的備份可以分為冷備和熱備。而mysqldump則一般作為冷備的手段,可以在業務低峰期間進行邏輯備份。為了保證並發度和數據的一致性,一般在進行備份的時候會使用–single-trasaction來開啟一個事務,利用Mysql的可重復讀事務隔離級別的一致性視圖來保證數據的一致性。
但是,使用mysqldump進行備份時,也有可能造成線上業務的性能抖動。因為Mysql需要將磁盤當中的數據全部都加載到內存當中,這就需要Buffer Pool利用內存淘汰機制淘汰一部分數據頁。而這些數據頁則很有可能是熱點數據。而對於線上業務而言,大量的請求達到引擎之后,發現數據不在內存當中就需要去磁盤上加載數據頁,但是內存卻一直被更新線程占用,造成大量數據頁等待flush,因此整個線上業務就可能被假阻塞。更加可怕的是,對於磁盤大量IO的影響是暫時性的,在后台線程備份結束之后就可以結束,而由於大量熱點數據被刷出內存造成的內存命中率降低而導致的影響則是長遠的。需要線上業務長時間運行在能夠逐漸恢復。
三 有可能是線上業務在等鎖
對於線上業務性能抖動,還有一種可能就是線上的查詢被鎖給阻塞了。在這里有可能的阻塞分別為MDS鎖和行鎖。對於MDS鎖來說非常好排查,我們可以使用show processlist命令去看線程狀態。
由於我們在show processlist只能看到被阻塞線程的詳情,對於到底是那個表占有了MDS鎖卻一無所知。而具體的排查方法,在有了performance_schema庫和sys庫之后變得更加方便了。我們可以在數據庫啟動時設置performance_schema=ON(Mysql5.6之后默認開啟),並且在sys.schema_table_lock_waits表中查看詳細數據。而對於行鎖的排查,我們則可以到sys.innodb_lock_waits表中查看其詳細信息這里不展開描述。
四 有可能是長事務導致的性能抖動
我們知道在Mysql中實現了四種隔離級別:讀未提交,讀已提交,可重復讀,序列化。而MVCC則是為了實現讀已提交,可重復讀兩種隔離級別而產生的版本控制方法。在可重復讀中,Mysql會在事務啟動時開啟一個一致性視圖,記錄下當前的事務id。若有其他的並發事務對這個數據行進行了修改,則會將其修改過程記錄到undo log當中,當該事務進行一致性讀的時候,會應用undo log來生成該事務對應的數據版本,一次來保證事務的隔離級別。
在長事務當中,大量的undo log 無法得到釋放,並且如果對於某一行會有大量的並發事務進行操作,那么在該事務進行讀取時,則會應用大量的Undo log來計算當前事務所能看到的數據版本,則也可能時造成線上業務性能抖動的原因。
附錄:
-
mysql臟頁:
當內存數據頁和磁盤數據頁上的內容不一致時,我們稱這個內存頁為臟頁;
內存數據寫入磁盤后,內存頁上的數據和磁盤頁上的數據就一致了,我們稱這個內存頁為干凈頁。 -
刷臟頁的時機:
(1)redo log寫滿時,沒有看見了,此時需要將checkpoint向前推進,推進的這部分日志對應的臟頁刷入到磁盤,此時所有的更新全部阻塞,此時寫的性能變為0,必須待刷一部分臟頁后才能更新。
(2)系統內存不足時,需要將一部分數據頁淘汰掉,如果淘汰的是臟頁,需要先將臟頁同步到磁盤。
(3)MySQL認為空閑的時間,這種沒有性能問題。
(4) mysql正常關閉之前,會把所有臟頁刷入磁盤,不存在性能問題。
作者:Lee_8f69
鏈接:https://www.jianshu.com/p/6ffb01aa7717
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。