count(*)、count(1)、count(字段)都有什么区别?
在我们的日常开发中,经常会遇到计算一张表的行数的情况,通常情况下我们使用一条select count(*) from t
语句就完事了。但是大家也可能看到count()函数的别的用法,比如count(1)
、count(字段名)
,那么他们和count(*)
有什么区别呢?count(*)
又是如何进行查询得到我们要的数据呢?本文就带大家一一揭晓。
一、count(*)是如何实现的
首先我们要知道,在不同的存储引擎中,count(*)的实现是不一样的。以select count(*) from t
这条语句来说:
- 在MyISAM中,由于每个表的总行数会作为一个变量存储在磁盘上,所以执行查询的时候只用返回这个变量的值就好了,查询速度比较快;
- 而对于我们常用的InnoDB来说,它会一行一行的将数据读取出来,然后对读取的数据进行累计计数,最后得到结果,查询会慢一些;
这里大家可能就想到了,为什么InnoDB不像MyISAM那样也将总行数存储起来呢?
注意了,我们这里的读是快照读,对于快照读来说,InnoDB是通过MVCC进行控制的,也就是说我们是可能会读取到历史数据的,因此读取的总行数并不是确定的,当然不能一视同仁的返回同一个值了。
如果你还不了解MVCC,可以看我的这篇文章:MVCC详解,深入浅出简单易懂_lans_g的博客-CSDN博客_mvcc
InnoDB的设计者当然也意识到了这个问题,所以他对count(*)操作进行了一定的优化。
对于上面的这条查询语句来说,无非就是要得到表的总行数嘛,表中的字段值我们并不关心。InnoDB是索引组织表,主键索引树的叶子节点是数据,而普通索引树的叶子节点是主键值。所以,普通索引树比主键索引树小很多。对于count(*)这样的操作,遍历哪个索引树得到的结果逻辑上都是一样的。因此,MySQL优化器会找到最小的那棵索引树来遍历,这样就提高了检索效率。
count(参数)是一个聚合函数,对于某一个结果集逐行进行判断,如果该行参数不为null,则+1,最后返回累计值。由于count(*)肯定不为null,所以经过优化后,在进行查询时并不会取出字段的值,而是逐行进行累加,有多少行就返回几。
二、对比count(1)、count(字段名)
除了count(*),大家可能会看到这样的写法:count(1)。这是什么意思呢?
对于count(1)
来说,InnoDB遍历整张表的时候,同样不会取出字段的值,而是对于返回的每一行后面加一个数字“1”,然后按行累加。是不是和count(*)很类似?事实上,他们的执行效率也是差不多的。
上文我们说了,count(参数)
函数会对参数不为null的行进行计数,因此对于count(字段)
来说,会先读取该字段中的值,判断不为null后,再累加。
三、总结一下
由于count(1)和count(*)并不会取出字段中的值,因此效率是比较高的,如果按照效率排序的话:
count(字段)<count(1)≈count( * )。
不过既然count( * )已经优化了这么多,推荐大家使用count(*)就好了。