現象
某個資料庫經常在某個時間點,比如淩晨 2 點或者白天某些時間段發出如下報警:
[Critical][prod][mysql] - 超200 kill SQL/分鐘[P0][PROBLEM][all(#2) db_data.Com_kill db=XXXX[m]:3306 10.53333>=3.3][O1 2019-11-01 03:40:00]
報警的意思是每分鐘超過 200 個 SQL 被 kill,是一個嚴重告警級别,會打電話給 DBA。大半夜報警的确令人不爽,那麼如何解決這個問題呢?
通過檢查日志,我們發現被 kill 的 SQL 都是 delete 語句。業務方其實會定時的跑删除任務,這個任務涉及到 N 多個表,删除任務持續時間比較長,是以白天和晚上都有一定機率會觸發 sql-killer,然後報警。
在有贊的資料庫運維體系中,每個執行個體都會配置一個 sql-killer 的實時工具,用于 kill query 超過指定門檻值的 SQL 請求(類似 pt-killer)。
初步分析
在之前的案例分析過程中,碰到過因為長事務導緻特定表上面的查詢耗時增加的問題。經分析發現,這次被 kill 的 SQL 是分布在各個表上面,而且查詢發現并不存在長事務。
分析問題發生時候的資料庫快照資訊,QPS 都很低,除了差不多 10 TPS 的 delete 和幾十的 select,沒有發現有問題的 SQL。
分析當時的 show engine innodb status 的資訊,發現每次出問題的時候都會出現一些 latch 的等待,如下圖所示。
找到對應的代碼行數
看代碼鎖位置像是在等待各種 Buffer Pool 的各種 latch。為啥會等待在這裡呢,又沒有 DDL 相關的 SQL,于是百思不得其解。
問題診斷一時間陷入困境。
抽絲剝繭
由于等待和 Buffer Pool 的各種 latch 相關,而且 delete 操作本身會産生大量髒資料,那會不會跟刷髒頁操作相關呢?
我們看下 SQL 被 kill 的量和刷髒頁的量之間的關系
發現每秒刷髒頁的量和 SQL 被 kill 的量的曲線有點相近,看着刷髒頁的量挺大的,但是每秒 delete 的 TPS 又不是很高,為啥這麼低的 TPS 會讓刷髒頁頻率抖動以及 SQL 執行變慢呢?
曾經換過不同批次的機器,發現問題依舊,并沒有改善,說明并不是機器本身的問題。
繼續浏覽 buffer pool 相關的監控名額,像是發現新大陸一樣的發現了一個異常名額
髒頁比例達到了快 90% !!!太吓人了!!!
為啥髒頁比例會達到 90% 呢,無非就是刷髒頁的速度跟不上産生的速度,要麼就是 IO 能力不行,要麼就是産生髒頁的速度過快,要麼就是記憶體池太小,導緻 Buffer Pool 被髒頁占滿。
那麼這個髒頁比例達到快 90% 會有什麼問題呢?
MySQL 有兩個關于髒頁的參數
select * from tab where a=1 and b=2;
我們檢視下官方定義
innodb_max_dirty_pages_pct:
InnoDB tries to flush data from the buffer pool so that the percentage of dirty pages does not exceed this value. The default value is 75.
innodb_max_dirty_pages_pct 是為了避免髒頁比例大于 75%,改變該參數不會影響刷髒頁的速度。
innodb_max_dirty_pages_pct_lwm:
Defines a low water mark representing the percentage of dirty pages at which preflushing is enabled to control the dirty page ratio. The default of 0 disables the pre-flushing behavior entirely.
innodb_max_dirty_pages_pct_lwm 表示的是當髒頁比例達到該參數表示的低水位時候,刷髒線程就開始預刷髒來控制髒頁比例,避免達到innodb_max_dirty_pages_pct 。刷髒頁的最大 IO 能力是受 innodb_io_capacity 和 innodb_io_capacity_max 控制。
生産上我們将 innodb_max_dirty_pages_pct_lwm 設定成了50
當髒頁比例大于 innodb_max_dirty_pages_pct 時候,InnoDB 會進行非常激烈的刷髒頁操作,但是由于 DELETE 操作還是在進行,髒頁産生的速度還是非常快,刷髒頁的速度還是跟不上髒頁産生的速度。為了避免髒頁比例進一步擴大,更新将會被堵塞,進而導緻 DELETE 執行變慢,直至被 KILL。
發現問題之後,根據我們之前的假設,有三種解決方案:
1. 調大 io_capacity ,但是由于主機是多執行個體部署,IO 占用已經比較高,PASS。
2. 降低髒頁産生速度,也就是調低 DELETE 速度,因為資料産生的速度很快,為了避免删除跟不上插入的速度,也被 PASS。
3. 調大 Buffer Pool,可以容納更多的髒頁。
說幹就幹,得益于 MySQL 5.7 的線上調整 Buffer Pool,立馬将 Buffer Pool Size 擴了一倍,效果非常顯著
髒頁比例立馬下降,被 kill 的 SQL 也下降了。平均 SQL rt 下降很多。
總結
得益于 MySQL 的開源,很多錯誤都可以直接确認到對應的代碼,大緻定位到問題發生的地方,給問題排查帶來了很多友善。同時對 MySQL buffer pool 的命中率以及髒頁比例也要多多關注,對 SQL 的性能都有很大的影響。