重新认识MySQL中的COUNT语句
在数据库的增删改查操作中,使用最频繁的就是查询操作。
而在所有查询操作中,统计数量操作更是经常被用到。
关于数据库中行数统计,无论是mysql还是oracle亦或者是sqlserver,都有一个函数可以使用,那就是count。
而对于count,有几个问题很值得去思考:
1、count有几种用法?
2、count(字段名)和count()的查询结果有什么不同?
3、count(1)和count()之间有什么不同?
4、count(1)和count()之间的效率哪个更高?
5、为什么《阿里巴巴java开发手册》建议使用count()
6、mysql的myisam引擎对count()做了哪些优化?
7、mysql的innodb引擎对count()做了哪些优化?
8、上面提到的mysql对count()做的优化,有一个关键的前提是什么?
9、select count() 的时候,加不加where条件有差别吗?
10、count()、count(1)和count(字段名)的执行过程是怎样的?
如果以上10道题,全部准确无误的回答的话,那说明你真的很了解count函数了,如果有哪些知识点是不了解的,那么本文正好可以重新帮你认识一下count,也为数据库优化做一些思考。
认识count
关于count函数的介绍:
1、count(expr) ,返回select语句检索的行中expr的值不为null的数量。结果是一个bigint值。
2、如果查询结果没有命中任何记录,则返回0
3、但是,值得注意的是,count() 的统计结果中,会包含值为null的行数。
例:
create table #bla(id int,id2 int) insert #bla values(null,null) insert #bla values(1,null) insert #bla values(null,1) insert #bla values(1,null) insert #bla values(null,1) insert #bla values(1,null) insert #bla values(null,null)
使用语句count(),count(id),count(id2)查询结果如下:
select count(),count(id),count(id2) from #bla results 7 3 2
除了count(id)和count()以外,还可以使用count(常量)(如count(1))来统计行数,那么这三条sql语句有什么区别呢?
到底哪种效率更高呢?
为什么《阿里巴巴java开发手册》中强制要求不让使用 count(列名)或 count(常量)来替代count()呢?
count(列名)、count(常量)和count()之间的区别
前面我们提到过count(expr)用于做行数统计,统计的是expr不为null的行数,那么count(列名)、 count(常量) 和 count()这三种语法中,expr分别是列名、 常量 和 。
那么列名、 常量 和 这三个条件中,常量是一个固定值,肯定不为null。
可以理解为查询整行,所以肯定也不为null,那么就只有列名的查询结果有可能是null了。
所以, count(常量) 和 count()表示的是直接查询符合条件的数据库表的行数。
而count(列名)表示的是查询符合条件的列的值不为null的行数。
除了查询得到结果集有区别之外,count()相比count(常量) 和 count(列名)来讲,count()是sql92定义的标准统计行数的语法,因为他是标准语法,所以mysql数据库对他进行过很多优化。
sql92,是数据库的一个ansi/iso标准。
它定义了一种语言(sql)以及数据库的行为(事务、隔离级别等)。
count()的优化
前面提到了count()是sql92定义的标准统计行数的语法,所以mysql数据库对他进行过很多优化。
那么,具体都做过哪些事情呢?
这里的介绍要区分不同的执行引擎。
mysql中比较常用的执行引擎就是innodb和myisam。myisam和innodb有很多区别,其中有一个关键的区别和我们接下来要介绍的count()有关,那就是myisam不支持事务,myisam中的锁是表级锁;
而innodb支持事务,并且支持行级锁。
因为myisam的锁是表级锁,所以同一张表上面的操作需要串行进行,所以,myisam做了一个简单的优化,那就是它可以把表的总行数单独记录下来,如果从一张表中使用count()进行查询的时候,可以直接返回这个记录下来的数值就可以了,当然,前提是不能有where条件。
myisam之所以可以把表中的总行数记录下来供count()查询使用,那是因为myisam数据库是表级锁,不会有并发的数据库行数修改,所以查询得到的行数是准确的。
但是,对于innodb来说,就不能做这种缓存操作了,因为innodb支持事务,其中大部分操作都是行级锁,所以可能表的行数可能会被并发修改,那么缓存记录下来的总行数就不准确了。
但是,innodb还是针对count()语句做了些优化的。
在innodb中,使用count()查询行数的时候,不可避免的要进行扫表了,那么,就可以在扫表过程中下功夫来优化效率了。
从mysql 8.0.13开始,针对innodb的select count() from tblname语句,确实在扫表的过程中做了一些优化。
前提是查询语句中不包含where或group by等条件。
我们知道,count()的目的只是为了统计总行数,所以,他根本不关心自己查到的具体值,他如果能够在扫表的过程中,选择一个成本较低的索引进行的话,那就可以大大节省时间。
我们知道,innodb中索引分为聚簇索引(主键索引)和非聚簇索引(非主键索引),聚簇索引的叶子节点中保存的是整行记录,而非聚簇索引的叶子节点中保存的是该行记录的主键的值。
所以,相比之下,非聚簇索引要比聚簇索引小很多,所以mysql会优先选择最小的非聚簇索引来扫表。
当我们建表的时候,除了主键索引以外,创建一个非主键索引还是有必要的。
至此,我们介绍完了mysql数据库对于count()的优化,这些优化的前提都是查询语句中不包含where以及group by条件。
count()和count(1)
介绍完了count(),接下来看看count(1),对于,这二者到底有没有区别,网上的说法众说纷纭。
有的说count()执行时会转换成count(1),所以count(1)少了转换步骤,所以更快。
还有的说,因为mysql针对count()做了特殊优化,所以count()更快。
那么,到底哪种说法是对的呢?
看下mysql官方文档是怎么说的:
innodb handles select count() and select count(1) operations in the same way. there is no performance difference.
画重点:
same way , no performance difference。
所以,对于count(1)和count(),mysql的优化是完全一样的,根本不存在谁比谁快!
那既然count()和count(1)一样,建议用哪个呢?
建议使用count()!
因为这个是sql92定义的标准统计行数的语法,而且本文只是基于mysql做了分析,关于oracle中的这个问题,也是众说纷纭的呢。
count(字段)
最后,就是我们一直还没提到的count(字段),他的查询就比较简单粗暴了,就是进行全表扫描,然后判断指定字段的值是不是为null,不为null则累加。相比count(),count(字段)多了一个步骤就是判断所查询的字段是否为null,所以他的性能要比count()慢。
总结
本文介绍了count函数的用法,主要用于统计表行数。
主要用法有count()、count(字段)和count(1)。
因为count()是sql92定义的标准统计行数的语法,所以mysql对他进行了很多优化,myisam中会直接把表的总行数单独记录下来供count()查询,而innodb则会在扫表的时候选择最小的索引来降低成本。当然,这些优化的前提都是没有进行where和group的条件查询。
在innodb中count()和count(1)实现上没有区别,而且效率一样,但是count(字段)需要进行字段的非null判断,所以效率会低一些。
因为count()是sql92定义的标准统计行数的语法,并且效率高,所以请直接使用count()查询表的行数!*
本文来自> 重新认识mysql中的count语句
上一篇: java存储数据到本地txt文件中