前言:最近在閱讀Innodb IO相關部分的源代碼。在閱讀之前一直有個疑問,show global status 中有兩個指標innodb_data_reads 和 innodb_data_read。兩個計數器僅差一個字母,他們之間的含義到底有何差別呢?本文將通過解析這兩個參數的含義,分析Innodb對於磁盤IO相關的一些知識
首先我們來看下MySQL官方文檔里對於這兩個參數的解釋:
-
The amount of data read since the server was started.
-
The total number of data reads.
文檔對於這兩個參數的解釋非常曖昧。這里的讀是指什么?是從哪里讀?磁盤還是內存?甚至連計數器的單位也不知道。
詳細解析:
接下來我們從源代碼來解析這兩個參數。在src/Srv0srv.c中存在對這兩個參數和代碼變量的映射關系的定義代碼,如下:
export_vars.innodb_data_reads = os_n_file_reads;
接着讓我們來看下這兩個代碼變量在哪里進行了累加操作。
os_n_file_reads++;
隨后,代碼即調用os_aio_array_reserve_slot將IO請求推入 simulated array,再根據wake_later標志位決定是否調用
回答這個問題,我們就需要對於innodb的simulated-aio和read-ahead算法有一定理解了。
進入到simulated的aio slot array的請求會有兩種,一種是通過buf_read_page 過來的普通頁的讀請求,一種是通過buf_read_ahead_random/linear 過來的預讀請求。從innodb的實現來說普通數據頁的請求是需要立即返回響應的,所以是同步(sync)IO。而對於預讀這樣數據並非SQL所需要,僅是用於提升性能的頁讀取,這樣的IO完全是可以異步的。這兩個差異也是導致simulated aio出現的原因。把IO請求推入slot array后,數據頁同步請求立即通知worker thread去os做同步IO。而預讀IO請求會不斷的推入slot array直到一次預讀所需要的page全部推入slot中,然后再會通知worker thread。此外在worker thread中,也會判斷一個segment的io請求是否相鄰連續,如果連續則把這些請求合並后,作為一次OS IO發到下層存儲中。
明白了這些也就不難理解為什么計數器要分開在不同的函數中計數了。如果累加都放在 _fil_io中進行,那么 innodb_data_read = 16K * innodb_data_reads (這里假設page沒有做壓縮)。然而在有了異步IO合並這個操作后,實際的innodb_data_reads會少於_fil_io中獲得的計數次數。所以,通過 innodb_data_read / innodb_data_reads得到的比值也可以推斷出一個MySQL實例中順序IO或者可順序預讀的IO比例。
我們在production環境的服務器上做一個驗證:
服務器A:在線用戶訪問的數據庫,猜測隨機IO較多
SHOW GLOBAL STATUS LIKE '%innodb_data_read%';
Innodb_data_read 46384854470656
Innodb_data_reads 2812101578
每次IO平均讀取字節數=16494
服務器B:歷史數據統計的數據庫,數據內容和服務器A完全一樣,猜測順序IO較多
SHOW GLOBAL STATUS LIKE '%innodb_data_read%';
Innodb_data_read 54835669766144
Innodb_data_reads 2604758776
每次IO平均讀取字節數=21052
可見順序IO較多的MySQL的單次IO讀取字節數確實要多於一個page的大小,說明IO合並效果明顯。
而隨機IO較多的MySQL的單詞IO讀取字節數幾乎和一個page大小一致,即16K
最后我們再總結下一些結論:
- Innodb_data_read 表示Innodb啟動后,從物理磁盤上讀取的字節數總和。
- Innodb_data_reads 表示Innodb啟動后,隊伍物理磁盤發起的IO請求次數總和。
- Innodb_data_read / Innodb_data_reads 得到的比值,越接近16K說明IO壓力越傾向於隨機IO,越遠離16K說明IO從順序預讀中獲得性能提升越多
參考資料:
1. InnoDB異步IO(AIO)實現詳解 http://hedengcheng.com/?p=98