【背景】
之前我們碰到一些MySQL的性能問題,比如服務器日志備份時可能會導致慢查詢增多,一句簡單的select或insert語句可能執行幾秒,IO負載較高的服務器更容易出現並發線程數升高,CPU上升等問題。
最近學習了MySQL InnoDB IO相關的部分內核原理,可以幫我們了解服務器IO瓶頸對MySQL性能的影響,下面以MySQL5.7.23的源碼為例
【原理】
1、InnoDB實現了同步IO和異步IO兩種文件讀寫方式
(1、)對於讀操作,通常用戶線程觸發的數據請求都是同步讀,其他后台線程觸發的是異步讀。
(2、)對於寫操作,InnoDB是WAL模式,先寫日志,延遲寫數據頁;
redo log的寫操作大部分是異步寫,主要在下面場景下觸發
<1、>redo log buffer空間不足時;
<2、>當參數innodb_flush_log_at_trx_commit設置為1時,每次事務提交都會做一次fsync,相當於是同步寫;
<3、>master線程每秒做一次redo fsync;
<4、>checkpoint
<5、>實例shutdown時
<.6、>binlog切換時
Page cleaner線程負責臟頁的刷新操作,其中double write buffer的寫磁盤是同步寫,數據文件的寫入是異步寫。
2、同步讀寫操作通常由用戶線程來完成,下面先分析同步讀
當用戶線程執行一句SQL時,如果請求的數據頁不在buffer pool中,就需要將文件中的數據頁加載到buffer pool中,
從函數buf_read_page可以看到這里是同步讀操作,如果IO有瓶頸,響應延遲,那么該線程就會被阻塞。
從函數buf_page_init_for_read可以看到,在讀數據頁時會加X鎖
這時如果有其他用戶線程請求相同的數據頁時,從函數buf_wait_for_read看到,嘗試獲取X鎖,就會處於阻塞狀態。
當服務器IO成為瓶頸,發生上面的問題時,就會出現SQL執行變慢
問題進一步惡化,大量慢查詢,運行中的線程處於等待狀態,占用了Innodb線程(innodb_thread_concurrency我們的配置大部分是0或64,實際上通常是CPU的邏輯核數40)
對於並發較高的系統,會導致其他大量的線程處於等待隊列中,並發線程過高又會導致上下文切換頻繁,CPU上升。
3、一個同步寫的例子
前面做過一個測試,執行500W條insert語句
用source執行insert腳本,TPS大約在每秒700,后面並行同時執行3個insert腳本,TPS達到每秒2000左右,IO %util已經接近100%
由於此時參數innodb_flush_log_at_trx_commit設置為1時,每次事務提交都會做一次fsync,相當於是同步寫,IO已達到瓶頸,TPS處理能力無法提高。
當將參數innodb_flush_log_at_trx_commit臨時調整為2,改為后台進程進行異步寫,並行執行8個insert腳本,TPS達到每秒約1W左右,IO %util約在8%。
實現邏輯可以關注log_write_up_to函數
【應用場景】
1、當服務器IO出現瓶頸,會導致MySQL性能大幅下降,因此建議盡可能的利用服務器內存資源,將實例的innodb_buffer_pool_size設置為物理內存的70%左右;
2、合理的拆分,盡可能的讓一個實例的熱點數據都可以緩存在innodb buffer pool中
3、對於某些場景下執行腳本,或初始化數據時,可以將innodb_flush_log_at_trx_commit臨時設置為2,能大幅提升導入性能。
參考資料:
http://mysql.taobao.org/monthly/2017/03/01/
http://mysql.taobao.org/monthly/2016/02/02/
http://mysql.taobao.org/monthly/2017/07/10/