【MySQL】【性能】大字段造成的統計卡頓

  創建三張表格,test,test1,test2,每張表插入十萬行數據,這三張表的schema都如圖1所示,不同的是每張表的des字段的長度不同(但同一張表格內,每一行des字段的長度都是相同的),如圖2所示。

圖1、schema
圖2、desc字段長度的對比

 
  重啓測試機,兩次執行sql查詢語句以統計每張表cnt字段的累加和,並獲取耗時如圖3、4所示

圖3、第一次統計
圖4、第二次統計

 
  對比圖3和圖4,會發現圖3的查詢時間遠大於圖4查詢的時間,這是因爲執行第一次統計的時候,三張表的數據都只存在磁盤裏面,mysql統計的時候,需要將三張表的數據先從磁盤裏面讀到內存數據頁裏面,然後才能進行統計操作,這發生了大量的隨機IO讀,從而導致統計緩慢。而執行完第一次統計之後,磁盤裏面的數據都被讀到內存頁裏面了,因此執行第二次統計的時候就無需再讀磁盤,直接統計內存頁裏面的數據即可,避免了隨機讀操作,所以第二次統計速度遠大於第一次統計速度。
  我們再看圖3,對比test1和test2表格的統計速度,發現test2的統計時間遠遠大於test1的統計時間,這是爲什麼?我們回到圖2,發現test2的des字段的長度(7168)遠大於test1的des字段長度(0),是了沒錯,這就是造成test2比test1慢的原因,我們知道,如果mysql要讀取字段cnt的值的話,它需要讓InnoDB把一整行數據都讀入到內存當中,然後才能去讀cnt的值,所以如果一行數據過長的話,那麼需要從磁盤中讀取的數據量就多了,特別是執行統計類的操作,隨機讀IO會大大增加,這就造成了圖3中test1與test2對比的情況。
  說到這裏,細心的讀者可能發現,不對啊,表test的des字段可是要比表test2的des字段大多了,那爲啥圖3中test的統計時間卻比test2的小那麼多。要解釋這個問題,就不得不提InnoDB的行溢出的概念了,我們知道InnoDB是以頁爲單位來存儲數據的(一個頁裏有多行數據),頁的大小爲16KB,也就是16384個字節,而我們的表格test光是des字段就有32768個字節,顯然這一行數據就已經超過了一個頁的大小,造成了行溢出。一般情況下,InnoDB都是將數據存放在B-tree Node的頁類型當中,也就是那個16KB的頁,但是發生了行溢出時,InnoDB會將溢出的行分爲兩部分,一部分放在B-tree Node的頁類型中,另一部分放在Uncompress BLOB頁中,如圖5所示,B-tree Node頁中存放着前768個字節的前綴數據,之後跟的偏移量,指向Uncompress BLOB頁。所以我們的test表的InnoDB行中其實只存放了字段des的前綴和相應的偏移量,一共幾百字節,比test2要小的多,那test的統計時間自然是要小於test2的。

圖5、行溢出的存儲方式

 
  如果對行溢出感興趣的讀者可以翻閱《MySQL技術內幕 InnoDB存儲引擎 第2版》一書,這裏就不贅述了。
  我們再看看有沒有方法能夠優化表test2的查詢速度,答案是有的,我們知道,innodb是以B+樹的方式來組織索引結構的[1],其中主鍵索引樹的葉子節點存的是整行數據,非主鍵索引樹的葉子節點存的是主鍵的值,我們當前的統計之所以這麼慢是因爲我們從主鍵索引樹的葉子節點中讀了整行數據,倘若我們能夠爲字段cnt建立索引,那麼我們統計的時候就無需去遍歷主鍵索引樹了,直接遍歷非主鍵索引樹即可,就避免了讀整行數據,減小了隨機讀IO。
執行語句

ALTER TABLE test2 CREATE INDEX ix_cnt(`cnt`) USING BTREE;

重啓測試機後查詢時長如圖6所示

圖6、優化後統計時長

 
[1](極客時間)深入淺出索引(上).

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章