以下分析count()操作均指不带where条件的情况,后面不再在特别强调
InnoDB 存储引擎:
先说结论,mysql5.5-5.6版本:性能由高到低排序为:
结论一:count(*) = count(主键) = count(最短二级索引字段) ≈ count(1) >= count(其他索引字段) >count(普通字段)
mysql5.7及以上版本:性能由高到低排序为:
结论二:count(*) = count(主键) = count(所有非空二级索引) ≈ count(1) > count(其他索引字段)>count(普通字段)
特别注意:结论二是mysql设计理想执行效果,但实际执行时,count(*)不一定是性能最佳的,在文末我会提到,这里暂且先按这个结论来。
看到上面结论,可能有人会说卧槽
- 在任何版本,
count(*) = count(主键) ≈ count(1)性能均为最佳 - mysql5.5-5.6版本:
count(最短二级索引字段) = count(*),性能取决于索引长短,越短越快 - mysql5.7及以上:
count(所有非空二级索引) = count(*),只要将二级索引约束非空,则无需再关心该索引长短,性能均最佳
下面来进行具体分析:
统计条数方式:
方式一:遍历聚簇索引树叶子节点data数据(全表扫描)
这种方式执行速度最慢

方式二:遍历聚簇索引树叶子节点主键
图与方式一 一致,实际也是全表扫描,但只需要根据主键索引统计条数,无需判断data里的对应的数据列值是否为NULL,所以速度优于方式一。
方式三:遍历二级索引树

速度优于方式二,我们知道,一个磁盘块大小固定是16k,聚簇索引树的叶子节点存储主键值和实际行数据,而二级索引树的叶子节点存储索引字段值和主键值,假设主键和二级索引字段大小为8b,一行数据大小为1k,那我们遍历一个磁盘块,方式一和方式二可以统计约16条数据,而方式三可以统计到1024条!如果二级索引字段长度更小,我们将可以在同一个磁盘块中统计到更多的数据。
方式四:无需扫描索引树,直接返回存储引擎记录的条数
该方式仅在mysql5.7及以上版本和MyISAM存储引擎中使用。速度最快,用mysql解释器解释此类型语句,将得到:Select tables optimized away
分析count()方法采用的统计方式:
1.count(*)、count(主键)、count(1):
mysql优化器对语义进行了优化,均采用最佳的方式进行统计,count(*)与count(主键)经优化后没有任何区别!count(1)并非按实际数据列进行统计,而是在每一行默认添加了一个值为1的字段,然后统计该字段的个数。那么我们可以举一反三,count(0),count('张三丰')… 是不是都是合法语义?(当然合法)。我们可以得出:count(*)性能=count(主键)≈count(1)。
优化器策略:
mysql5.7及以上版本:
- 仅仅选择方式四进行统计(Select tables optimized away)
测试:选择任意表,要求有建立主键。我这里是basic_user表,主键为fd_id),执行下面语句:
EXPLAIN SELECT COUNT(*) FROM basic_user;
EXPLAIN SELECT COUNT(fd_id) FROM basic_user;
EXPLAIN SELECT COUNT(1) FROM basic_user;结果均为:

此处table为null,type为null,说明查询时既不需要访问表,也不需要访问索引,即直接返回条数。
mysql5.5-5.6:
- 优先使用统计方式三(即遍历二级索引树),且选择最短二级索引列,是否要求索引字段NOT NULL呢?不一定,即使为NULL,优化器依然会选择该索引列进行统计,且统计时不会忽略NULL值,当然,原则上设计表时,应该给该字段加上非空约束(加上则更快,可以减少非null判断)。
测试:选择任意表,要求建立主键和至少一个索引字段。我这里表是order_original_orders,主键为fd_id,最短索引字段为:fd_is_credit_pay(int型,允许为NULL,占5个字节),其他索引有fd_bm_id(bigint型,允许为NULL,占9个字节),执行下面语句:
EXPLAIN SELECT COUNT(*) FROM order_original_orders;
EXPLAIN SELECT COUNT(fd_id) FROM order_original_orders;
EXPLAIN SELECT COUNT(1) FROM order_original_orders;结果均为:

说明mysql优化器确实优先选择了最短索引列进行统计,我们人为将fd_is_credit_pay增加一些NULL值,
UPDATE order_original_orders SET fd_is_credit_pay = NULL WHERE fd_id in (1,2,3);
再执行上面explain操作,会发现结果是一致的,且与count(*)实际执行结果的条数也是一致的,说明当优化器使用索引字段进行统计时,不会忽略NULL值,请注意,这与直接count(fd_is_credit_pay)是不一样的,直接count(索引字段),将会忽略NULL值。
- 当数据表没有二级索引时,优化器将选择方式二,即主键索引进行统计
测试:选择任意表,要求有建立主键且不存在其他索引,我这里表是system_module,主键为fd_id,不存在其他索引列,执行下面语句:
EXPLAIN SELECT COUNT(*) FROM system_module;
EXPLAIN SELECT COUNT(fd_id) FROM system_module;
EXPLAIN SELECT COUNT(1) FROM system_module;执行结果均为:

2.count(二级索引列):
mysql5.7及以上版本:
只要将任一索引约束为NOT NULL(不关心索引长短),将获得mysql优化器优化,即count(所有非空二级索引) = count(*);若允许为NULL,则通过遍历二级索引树进行统计。
测试:
我这里使用的表为basic_user,主键是fd_id,二级非空索引字段fd_name(varchar[10]型,NOT NULL,占10个字节),普通索引字段fd_age(tinyint型,允许为NULL,占2个字节)
执行:
EXPLAIN SELECT COUNT(fd_name) FROM basic_user;结果:

发现确实得到了mysql优化器优化,与count(*)是一致的!
执行:
EXPLAIN SELECT COUNT(fd_age) FROM basic_user;结果:

我们发现fd_age索引长度更短,但仍然是走二级索引树进行统计!所以说,count(索引字段)只关心是否加非空约束,加了则性能与count(*)一致,未加,则索引越短越快,即:count(较短的索引字段) > count(较长的索引字段)。那么mysql优化器为什么不优化count(允许为NULL索引字段)?因为需要遍历该索引的每个值,忽略NULL值进行统计。
mysql5.5-5.6:
mysql优化器不进行优化,所有的count(索引字段)全部执行遍历二级索引树操作,且统计时忽略NULL值,索引字段越短,速度越快。即:count(*)=count(最短索引字段)>count(其他索引字段)。原因在讲count(*)优化的时候已经提到了,count(*)经优化后,就是优先选择最短索引列进行统计,不记得了可以返回上文查看。
3.count(普通字段):
mysql优化器不进行优化,使用统计方式一进行统计(即全表扫描),需要忽略NULL值,速度最慢。
测试:选择表任意非索引字段,执行explain count(普通字段),即可发现是走全表扫描。

MyISAM 存储引擎:
结论:
count(*)=count(主键)≈count(1)
MyISAM存储引擎记录了表数据行数,与mysql5.7及以上版本是类似的,执行count(*)、count(主键)、count(1)均会被优化器优化,不再具体阐述。
总之:不管是InnoBD还是MyISAM,任何情况都使用count(*)就ok了!万无一失!
特殊说明:
在说InnoBD结论二(mysql5.7及以上版本)中,我说count(*) 不一定性能最佳,处进行说明:
仅有一种情况,出现count(允许为NULL索引字段)>count(*)(除业务需要,一般不会允许为NULL),因为一旦加上NOT NULL约束,优化器将优化成与count(*)一致。
条件:mysql5.7及以上版本、数据表中有允许为NULL的二级索引
我的表为order_original_orders,最短索引为fd_is_credit_pay(int型,允许为null,占5个字节)。
- 先执行count(*):
SELECT COUNT(*) FROM order_original_orders;查询结果:

耗时:

查询方式:(方式四,直接返回条数)

- 再执行count(fd_is_credit_pay):
查询结果:

耗时:

查询方式:(方式三,遍历索引树)

反复执行多次发现,count(允许为NULL索引字段)速度远远快于count(*),所以直接返回条数并非一定快于遍历二级索引树。至于为什么count(*)经优化器优化后,竟然还没有count(允许为NULL索引字段)快,之后再进行分析。
文中如有不正确观点,敬请指正!
















