MySQL · 引擎特性 · InnoDB COUNT(*) 优化(?)
在5.7版本中,InnoDB實現了新的handler的records接口函數,當你需要表上的精確記錄個數時,會直接調用該函數進行計算。
使用
實際上records接口函數是在優化階段調用的,在滿足一定條件時,直接去計算行級計數。其explain出來的結果相比老版本也有所不同,這里我們使用sysbench的sbtest表來進行測試,共200萬行數據。
mysql> show create table sbtest1\G *************************** 1. row *************************** Table: sbtest1 Create Table: CREATE TABLE `sbtest1` (`id` int(10) unsigned NOT NULL AUTO_INCREMENT,`k` int(10) unsigned NOT NULL DEFAULT '0',`c` char(120) NOT NULL DEFAULT '',`pad` char(60) NOT NULL DEFAULT '',PRIMARY KEY (`id`),KEY `k_1` (`k`) ) ENGINE=InnoDB AUTO_INCREMENT=2000001 DEFAULT CHARSET=utf8 MAX_ROWS=1000000 1 row in set (0.00 sec)mysql> explain select count(*) from sbtest1\G *************************** 1. row *************************** id: 1select_type: SIMPLE table: NULLpartitions: NULL type: NULL possible_keys: NULL key: NULL key_len: NULL ref: NULL rows: NULL filtered: NULL Extra: Select tables optimized away 1 row in set, 1 warning (0.00 sec)注意這里Extra里為”Select tables optimized away”,表示在優化器階段已經被優化掉了。如果給id列帶上條件的話,則回退到之前的邏輯
mysql> explain select count(*) from sbtest1 where id > 0\G *************************** 1. row *************************** id: 1select_type: SIMPLE table: sbtest1partitions: NULL type: range possible_keys: PRIMARY key: PRIMARY key_len: 4 ref: NULL rows: 960984 filtered: 100.00 Extra: Using where; Using index 1 row in set, 1 warning (0.00 sec)實現
在WL#6742中,為InnoDB實現了handler的records函數接口
函數棧
opt_sum_query |--> get_exact_record_count|--> ha_records|--> ha_innobase::records|-->row_scan_index_for_mysql- HA_HAS_RECORDS:引擎flag,表示是否可以把count(*)下推到引擎層
- 總是使用聚集索引來進行計算行數
- 只需要讀取主鍵值,無需去讀取外部存儲列(row_prebuilt_t::read_just_key),如果行記錄較大的話,就可以節省客觀的諸如內存拷貝之類的操作開銷
- 計算過程可中斷,每檢索1000條記錄,檢查事務是否被中斷
- 由于只有一次引擎層的調用,減少了Server層和InnoDB的交互,避免了無謂的內存操作或格式轉換
- 對于分區表,在5.7版本已經下推到innodb層,因此分區表的計算方式(ha_innopart::records)是針對每個分區調用ha_innobase::records,再將結果累加起來
相關代碼:
commit1
commit2
缺點
由于總是強制使用聚集索引,缺點很明顯:當二級索引的大小遠小于聚集索引,且數據不在內存中時,使用二級索引顯然要快些,因此文件IO更少。如下例:
默認情況下檢索所有行(以下測試都是在清空buffer pool時進行的):
mysql> select count(*) from sbtest1; +----------+ | count(*) | +----------+ | 2000000 | +----------+ 1 row in set (3.92 sec)即時強制指定索引也沒用 :(
mysql> select count(*) from sbtest1 force index(k_1); +----------+ | count(*) | +----------+ | 2000000 | +----------+ 1 row in set (3.86 sec)但如果帶上一個簡單的條件,讓select count(*)走索引k_1,耗費的時間立馬下降了….
mysql> select count(*) from sbtest1 where k > 0; +----------+ | count(*) | +----------+ | 2000000 | +----------+ 1 row in set (1.05 sec)個人認為這算是一個性能退化,退一步講,如果用戶知道force index能夠走一個更好的索引來計算行數,優化器應該做出選擇,而不是總是無條件選擇聚集索引,提了個Bug到官方
其他
從WL#6742還提到了一個尚未公布的WL#6605,從其只言片語中可以推斷官方有意向實現即時獲得行數:
The next worklog, WL#6605, is intended to return the COUNT(*) through this handler::records() interface almost immediately in all conditions just by keeping track if the base committed count along with transaction deltas讓我們繼續對新版本保持期待吧 :)
總結
以上是生活随笔為你收集整理的MySQL · 引擎特性 · InnoDB COUNT(*) 优化(?)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: UnicodeDecodeError解决
- 下一篇: RedHat 7配置FTP服务