数据库查询相信很多人都不陌生。有些人经常嘲笑程序员是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.SELECTCOUNT(*)时,加where条件有区别吗?10、COUNT(*)、COUNT(1)、COUNT(字段名)的执行过程是怎样的?如果你能准确回答以上10道题,说明你真的很了解COUNT函数。如果还有不懂的知识点,那么这篇文章正好可以帮你答疑解惑。一、认识COUNT关于COUNT函数,在MySQL官网上有详细的介绍:简单翻译:1.COUNT(expr),返回SELECT语句检索到的行中expr的值为的行数不为空。结果是一个BIGINT值。2、如果查询结果没有命中任何记录,则返回03。但是需要注意的是,COUNT(*)的统计结果会包含值为NULL的行数。即下表记录createtable#bla(idint,id2int)insert#blavalues(null,null)insert#blavalues(1,null)insert#blavalues(null,1)insert#blavalues(1,null)insert#blavalues(null,1)insert#blavalues(1,null)insert#blavalues(null,null)使用语句count(*),count(id),count(id2)查询结果如下:selectcount(*),count(id),count(id2)from#blaresults732除了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(列名),是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(*)查询行数时,免不了要扫表。然后,可以在表扫描过程中努力优化效率。从MySQL8.0.13开始,InnoDB的SELECTCOUNT(*)FROMtbl_name语句在表扫描过程中确实做了一些优化。前提是查询语句不包含WHERE或GROUPBY等条件。我们知道COUNT(*)的目的只是为了统计总行数,所以他根本不关心他找到的具体值是多少,所以如果他在表扫描过程中可以选择一个成本更低的索引,那可以节省很多时间。我们知道InnoDB中的索引分为聚集索引(主键索引)和非聚集索引(非主键索引)。聚集索引的叶子节点存储整行记录,而不是聚集索引的叶子节点。是行记录的主键的值。因此,相比之下,非聚集索引要比聚集索引小很多,所以MySQL会优先使用最小的非聚集索引来扫描表。所以我们在建表的时候,除了主键索引之外,还需要创建一个非主键索引。至此,我们介绍了MySQL数据库针对COUNT(*)的优化。这些优化的前提是查询语句不包含WHERE和GROUPBY条件。COUNT(*)和COUNT(1)介绍完COUNT(*),我们再来看COUNT(1)。关于两者有没有区别,网上众说纷纭。有人说COUNT(*)执行的时候会转换成COUNT(1),所以COUNT(1)的转换步骤少,所以速度更快。也有人说COUNT(*)更快,因为MySQL对COUNT(*)做了专门的优化。那么,哪种说法是正确的呢?看看官方的MySQL文档是怎么说的:InnoDB以相同的方式处理SELECTCOUNT(*)和SELECTCOUNT(1)操作。没有性能差异。关键点:相同的方式,没有性能差异。所以对于COUNT(1)和COUNT(*),MySQL的优化是一模一样的,没有谁比谁更快!由于COUNT(*)与COUNT(1)相同,因此推荐使用哪一种?建议使用COUNT(*)!因为这是SQL92定义的统计行数的标准语法,而本文只是基于MySQL的分析,所以Oracle内部对这个问题众说纷纭。COUNT(字段)最后,还有我们一直没有提到的COUNT(字段)。他的查询比较简单粗暴,就是扫描全表,然后判断指定字段的值是否为NULL,不为NULL则累加。与COUNT(*)相比,COUNT(field)多了一步判断查询字段是否为NULL,所以性能比COUNT(*)慢。小结本文介绍COUNT函数的用法,主要用于统计一个表的行数。主要用法是COUNT(*)、COUNT(field)和COUNT(1)。因为COUNT(*)是SQL92定义的统计行数的标准语法,所以MySQL对它做了很多优化。MyISAM会直接记录表中的总行数用于COUNT(*)查询,而InnoDB会在建表时扫描选择最小的索引来降低成本。当然,这些优化的前提是没有where和group的条件查询。InnoDB中COUNT(*)和COUNT(1)的实现没有区别,效率是一样的,但是COUNT(field)需要判断字段是否不为NULL,所以效率会低一些。因为COUNT(*)是SQL92定义的统计行数的标准语法,效率很高,请直接使用COUNT(*)查询表的行数!参考:https://dev.mysql.com/doc/refman/8.0/en/group-by-functions.html#function_count《极客时间——MySQL实战45讲》【本文为专栏作家Hollis原创文章,作者微信公众号Hollis(ID:hollishuang)】点此阅读该作者更多好文
