0、導讀
飽受诟病的InnoDB表COUNT(*)性能問題在5.7下做了優化,果真如此嗎?
1、經典需求:InnoDB表COUNT(*)
InnoDB引擎表經常被抱怨執行COUNT(*)的效率太差,是以此類需求通常會被建議用其他方法來滿足,比如另外加一個計數器表,或者用SHOW TABLE STATUS檢視大概數量。
不過,從MySQL 5.7.2起,這個問題得到了解決,我們來看看。
2、MySQL 5.7版本InnoDB對COUNT(*)的優化
MySQL每釋出一個新版本,都會放出相應的Release Notes,我們注意到5.7.2版本的釋出說明中提到:
InnoDB:statements now invoke a single handler call to the storage engine to scan the clustered index and return the row count to the Optimizer. Previously, a row count was typically performed by traversing a smaller secondary index and invoking a handler call for each record. A single handler call to the storage engine to count rows in the clustered index generally improves
SELECT COUNT(*) FROM t
performance. However, in the case of a large clustered index and a significantly smaller secondary index, performance degradation is possible compared to performance using the previous, non-optimized implementation. For more information, see Limits on InnoDB Tables.
SELECT COUNT(*) FROM t
簡單地說就是:COUNT(*)會選擇聚集索引,進行一次内部handler函數調用,即可快速獲得該表總數。我們可以通過執行計劃看到這個變化,例如:

很明顯,在查詢優化器階段就已經得到優化了,相比效率應該杠杠的吧,我們稍後再來對比看看。
補充說下,5.7以前的版本中,COUNT(*)請求通常是:掃描普通索引來獲得這個總數。也來看看5.6下的執行計劃是怎樣的:
可以看到,可以利用覆寫索引來完成COUNT(*)請求。
3、對比測試
先看一組測試資料:
count(*)對比測試 | MySQL 5.6.33 | MySQL 5.7.15 | 相差 |
表資料量 | 8976914 | 9000270 | 100.26% |
耗時(秒) | 5.459952 | 1.142340 | 20.92% |
可以看到,兩次資料量相當,但SQL耗時5.7約隻有5.6的1/5,這個效率還是不錯的吧。
我們來看看5.6和5.7版本下的status和profiling對比情況:
4、别高興得太早
看完上面的對比測試,相信您已經心動了吧,但還别高興得太早哦,官方文檔裡其實埋了一個伏筆:
SELECT COUNT(*) FROM t
SELECT COUNT(*) FROM t
簡言之,就是說如果聚集索引較大(或者說表資料量較大),沒有完全加載到buffer pool中的話,有可能反而會更慢,還不如用原先的方式。
下面我們來測試下,讀取tpcc測試表stock,該表有1億行記錄,表空間檔案約65GB,而innodb buffer pool隻配置設定了12G,這時候再看下對比資料:
1億 | 0.00% | ||
693.66 | 5331.69 | 768.63% |
在這種情況下,5.7版本反而慢的誇張,悲劇啊~
那麼在5.7下的大表,有沒有辦法仍舊采用以前的方法來做COUNT(*)統計呢。當然可以了,我們可以強制指定普通索引,不過還需要加上WHERE條件,否則還是不行。後來搜了下,發現這是個bug,印風(zhaiwx)已經報告給官方了,bug id:81854。
這次的SQL執行耗時和在5.6下的就基本一樣了。
4、後記
5.7版本整體挺贊的,不過還是有不少地方需要完善,期待能更成熟起來。
參考
1. MySQL 5.7.2 Release Notes:
http://dev.mysql.com/doc/relnotes/mysql/5.7/en/news-5-7-2.html2. Limits on InnoDB Tables:
http://dev.mysql.com/doc/refman/5.7/en/innodb-restrictions.html