上翻译:
1、COUNT(expr) ,返回SELECT语句检索的行中expr的值不为NULL的数量。结果是一个BIGINT值。
2、如果查询结果没有命中任何记录,则返回0
3、但是,值得注意的是,COUNT(*) 的统计结果中,会包含值为NULL的行数。
那么问题来了count(*) 、count(1) 、count(字段)用哪个?
最新版的阿里巴巴开发手册:
df2131ea004d4a68b41c3c5a1de39d59.png
SQL92,是数据库的一个ANSI/ISO标准。它定义了一种语言(SQL)以及数据库的行为(事务、隔离级别等)
另外:
5890444fd0cb421fbeea6c4397d129e9.png
说了啥?
1.对于存储引擎 InnoDB,存储精确的行数是有问题的。多个事务可能同时发生,每个事务都可能影响计数。
2。InnoDB不保留表中行的内部计数,因为并发事务可能同时 “看到”不同数量的行。因此,SELECT COUNT(*) 语句只计算对当前事务可见的行。
3.从 MySQL 8.0.13 开始,如果没有额外的子句(如或 ) ,则表的查询性能针对单线程工作负载进行了优化。 SELECT COUNT(*) FROM tbl_nameInnoDBWHEREGROUP BY
4.InnoDBSELECT COUNT()通过遍历最小的可用二级索引来处理语句,除非索引或优化器提示指示优化器使用不同的索引。如果二级索引不存在,则通过扫描聚集索引来InnoDB 处理SELECT COUNT()语句。
5.SELECT COUNT()如果索引记录不完全在缓冲池中,则 处理语句需要一些时间。为了更快地计数,创建一个计数器表并让您的应用程序根据它所做的插入和删除来更新它。但是,在数千个并发事务启动对同一计数器表的更新的情况下,此方法可能无法很好地扩展。如果近似行数就足够了,请使用 SHOW TABLE STATUS。
6.InnoDB处理SELECT COUNT()和SELECT COUNT(1) 操作方式相同。没有性能差异。
特别重点,官方文档说的:
InnoDB handles SELECT COUNT(*) and SELECT COUNT(1) operations in the same way. There is no performance difference.
没有啥区别!
接下来,给出区别及分析
一、执行结果
count(*) 和count(1) 都是统计行数,而count(字段) 是统计字段列非null的行数
二、不同的存储引擎
在不同的存储引擎中,count(*)函数的执行是不同的
MyISAM和InnoDB有很多区别,其中有一个关键的区别和我们接下来要介绍的COUNT(*)有关,那就是MyISAM不支持事务,MyISAM中的锁是表级锁;而InnoDB支持事务,并且支持行级锁。
因为MyISAM的锁是表级锁,所以同一张表上面的操作需要串行进行,所以,MyISAM做了一个简单的优化,那就是它可以把表的总行数单独记录下来,如果从一张表中使用COUNT(*)进行查询的时候,可以直接返回这个记录下来的数值就可以了,当然,前提是不能有where条件。
MyISAM之所以可以把表中的总行数记录下来供COUNT(*)查询使用,那是因为MyISAM数据库是表级锁,不会有并发的数据库行数修改,所以查询得到的行数是准确的。
但是,对于InnoDB来说,就不能做这种缓存操作了,因为InnoDB支持事务,其中大部分操作都是行级锁,所以可能表的行数可能会被并发修改,那么缓存记录下来的总行数就不准确了。
但是,InnoDB还是针对COUNT(*)语句做了些优化的。
InnoDB:
在InnoDB存储引擎中,count(*)函数是先从内存中读取表中的数据到内存缓冲区,然后扫描全表获得行记录数。在使用count函数中加上where条件时,在两个存储引擎中的效果是一样的,都会扫描全表计算某字段有值项的次数。
说点官话:mysql对于count(*) 的优化
InnoDB 是索引组织表,主键索引树的叶子节点是数据,而普通索引树的叶子节点是主键值。所以,普通索引树比主键索引树小很多。对于 count() 这样的操作,遍历哪个索引树得到的结果逻辑上都是一样的。因此,MySQL 优化器会找到最小的那棵树来遍历。因此表有二级索引,则使用二级索引key_len最小的索引进行扫描,尽管这个二级索引的key_len的值大于主键,都使用二级索引。在保证逻辑正确的前提下,尽量减少扫描的数据量,是数据库系统设计的通用法则之一。
通俗的说:
在InnoDB中,使用COUNT(*)查询行数的时候,不可避免的要进行扫表了,那么,就可以在扫表过程中下功夫来优化效率了。
从MySQL 8.0.13开始,针对InnoDB的SELECT COUNT(*) FROM tbl_name语句,确实在扫表的过程中做了一些优化。前提是查询语句中不包含WHERE或GROUP BY等条件。
我们知道,COUNT(*)的目的只是为了统计总行数,所以,他根本不关心自己查到的具体值,所以,他如果能够在扫表的过程中,选择一个成本较低的索引进行的话,那就可以大大节省时间。
我们知道,InnoDB中索引分为聚簇索引(主键索引)和非聚簇索引(非主键索引),聚簇索引的叶子节点中保存的是整行记录,而非聚簇索引的叶子节点中保存的是该行记录的主键的值。
所以,相比之下,非聚簇索引要比聚簇索引小很多,所以MySQL会优先选择最小的非聚簇索引来扫表。所以,当我们建表的时候,除了主键索引以外,创建一个非主键索引还是有必要的。
count(字段)
1、如果这个字段定义为not null的话,一行行地从记录里面读出这个字段,判断不能为null,按行累计加1
2、如果这个字段定义允许为null,一行行地从记录里面读出这个字段,执行的时候还要判断是否为null,不为null的按行累计加1,返回累加值
三、执行效率
1、如果在开发中需要用到count()聚合,那么优先考虑count(),因为mysql本身对于count()做了特别的优化处理。
有主键或联合主键的情况下,count()略比count(1)快一些。
没有主键的情况下count(1)比count()快一些。
如果表只有一个字段,则count(*)是最快的。
2、使用count()聚合函数后,最好不要跟where age = 1;这样的条件,会导致不走索引,降低查询效率。除非该字段已经建立了索引。使用count()聚合函数后,若有where条件,且where条件的字段未建立索引,则查询不会走索引,直接扫描了全表。
3、count(字段),非主键字段,这样的使用方式最好不要出现,因为它不会走索引。
count(主键ID)比count(1)慢的原因?
对于 count(主键 ID) 来说,InnoDB 引擎会遍历主键索引树,把每一行的ID值取出来,返回给server层,server层拿到ID后,判断是不可能为空的,按行累加加1,最后返回累计值。
对于count(1),InnoDB引擎会扫描主键索引树,但不取值,server层对于返回的每一行,按行累计加1,判断不可能为NULL,返回累计值。
从InnoDB引擎层返回ID会涉及到解析数据行、拷贝字段值的操作,因此count(主键 ID)执行要比count(1)执行慢。
count(主键id)走主键索引的时候效率较count(*)差的原因?
平时我们检索一列的时候,基本上等值或范围查询,那么索引基数大的索引必然效率很高(符合走主键索引查找速度最快的原则)。
但是在做count(*)的时候并没有检索具体的一行或者一个范围,那么选择基数小的索引对count操作效率会更高。在做count操作的时候,mysql会遍历每个叶子节点,所以基数越小,效率越高。mysql非聚簇索引叶子节点保存指向主键ID的指针,所以需要检索两遍索引。但是这里相对于遍历主键索引,即使检索两遍索引效率也比单纯的检索主键索引快。
Innodb是索引组织表,主键索引树的叶子节点是数据,而普通索引树的叶子节点是主键值,所以普通索引树小很多,索引长度越小树的大小就越小。
网友评论