数据库Count语句详解
数据库查询相信很多⼈都不陌⽣,所有经常有⼈调侃程序员就是CRUD专员,这所谓的CRUD指的就是数据库的增删改查。在数据库的增删改查操作中,使⽤最频繁的就是查询操作。⽽在所有查询操作中,统计数量操作更是经常被⽤到。关于数据库中⾏数统计,⽆论是MySQL 还是Oracle,都有⼀个函数可以使⽤,那就是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函数了,如果有哪些知识点是不了解的,那么本⽂正好可以帮你答疑解惑。1、认识COUNT关于COUNT函数,在MySQL官⽹中有详细介绍:简单翻译⼀下:1、COUNT(expr) ,返回SELECT语句检索的⾏中expr的值不为NULL的数量。结果是⼀个BIGINT值。2、如果查询结果没有命中任何记录,则返回03、但是,值得注意的
是,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 tbl_name语句,确实在扫表的过程中做了⼀些优化。前提是查询语句中不包含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(*)慢。
总结