在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: 1
- select_type: SIMPLE
- table: NULL
- partitions: 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: 1
- select_type: SIMPLE
- table: sbtest1
- partitions: 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
相关代码:
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
让我们继续对新版本保持期待吧 :)
来源: http://mysql.taobao.org/monthly/2016/06/10/