一 概要
最近在研究mybatis的时候发现**Mapper.xml在编写SQL的时候发现聚合函数(count())使用起来挺方便,count() 是一个聚合函数,对于返回的结果集,一行行地判断,如果 count 函数的参数不是 NULL,累计值就加 1,否则不加,最后返回累计值。
<select id="checkEmail" parameterType="string" resultType="int">
select count(*) from mark_user
where email = #{email}
</select>
此处使用的是count(*),而且在阿里巴巴开发手册中强制要求使用count(*),原文如下:
count(*),count(常量),count(field)的概述:
count(*) 和 count(常量)直接查询符合条件的数据库表的行数,而count(field)则会查询符合NOT NULL的数据库行数。此外count(*)时SQL92定义的标准统计行数的语法,而且MySQL数据库对其进行过很多优化。
SQL92,是数据库的一个ANSI/ISO标准。它定义了一种语言(SQL)以及数据库的行为(事务、隔离级别等)。
二 count(*),count(常量),count(field)的区别
执行条件 | 执行方法 | MySQL中执行的结果 | 不同count方法的总结 |
存在where语句 | count(*) | ![]() | count(常量)如(count(1)) 和 count(*)表示的是直接查询符合条件的数据库的行数,而 count(field)(field的可以理解为表的列名)表示的是查询符合条件的列的值不为NULL的行数。 |
count(1) | ![]() | ||
count(field) | ![]() | ||
![]() | |||
当数据库中某指定字段NULL时,如上图中phone字段的第5条数据为NULL | count(*) | ![]() | 对于 count(1) 来说:InnoDB 引擎遍历整张表,但不取值。对于返回的每一行,放一个数字“1”进去,判断是不可能为空的,按行累加。 对于 count(field) 来说: 1.如果这个字段定义为NOT NULL的时候,一行一行地从记录里面读取这个字段,判断不能为NOT NULL,按行累加; 2.如果这个字段允许为NULL的时候,在执行存在NULL的情况,这时候判断不是NULL的时候才会累加。 但是 count(*) 是例外,并不会把全部字段取出来,而是专门做了优化,不取值。count(*) 肯定不是 null,按行累加。 |
count(field) | ![]() | ||
count(1) | ![]() | ||
对于 NULL ,不为NULL 则累加。性能比count(1) 和count(*) 慢。所以,按照函数的执行效率排序为可以认为结果为: count(field) < count(主键 id) < count(1) ≈ count(*)。 |
三 count(*)的优化
MySQL中两种主要执行引擎:
1. InnoDB引擎:InnoDB支持事务,大部分操作行级锁,行可能被并行修改,那么缓存记录不准确。而且InnoDB针对count(*)通过低成本的索引进行扫表,而不关注表的具体内容。InnoDB中索引分为聚簇索引(主键索引)和非聚簇索引(非主键索引),聚簇索引的叶子节点中保存的时整行数据,而非聚簇索引的叶子节点保存到时该行记录的主键值。同时MySQL会优先选择最小的非聚簇索引来扫表。这些优化的前提是查询语句中不包含where条件和group by 条件。
2. MyISAM引擎:MyISAM做了一个简单的优化,把表的总行数单独记录下来,如执行count(*)时可以直接返回,前提时不能有where条件的。MyISAM时表级锁,不会有并发的行操作,所以查到的结果时准确的。
MyISAM不支持事务,MyISAM中的锁是表级锁,而InnoDB支持事务,并且支持行级锁。
四 总结
聚合函数count()主要是用于统计表行数,主要用法有count(*),count(1),count(主键id)和count(field);
其中count(*)是SQL92定义的标准统计行数的语法,MySQL对其进行了很多优化,MyISAM中会直接把表的总行数单独记录下提供给count(*),而InnoDB则会在扫表的时候选择最小的索引(非聚簇索引,其叶子节点保存的是主键值)来降低成本,前提是,查询语句中不能够存在where和group条件。
在InnoDB中,count(*) 和 count(1)实现上没有区别,效率等同,当时count(field)则需要对字段进行非UNLL判断,所以效率会有所下降。
综上所述可得为什么阿里巴巴开发手册上强制规定不能够用count(列名)和count(常量)来代替count(*);
对数据量为千万的表进行这三种查询:
根据结果可知,count(*)和count(1)的所耗时间最少,且接近,而count(field)所消耗的时间最多。