MySQL:SELECT COUNT 小结

MySQL:SELECT COUNT 小结

背景

今天团队在作线下代码评审的时候,发现同窗们在代码中出现了select count(1) 、 select count(*),和具体的select count(字段)的不一样写法,本着分析的目的在会议室讨论了起来,那这几种写法究竟孰优孰劣呢,咱们一块儿来看一下。html

讨论概括

先来看看MySQL官方对SELECT COUNT的定义:mysql

传送门:https://dev.mysql.com/doc/refman/5.6/en/aggregate-functions.html#function_countsql

 大概能够分下面这几个步骤讨论。数据库

COUNT(expr)的分析

COUNT(expr)函数返回的值是由SELECT语句检索的行中expr表达式非null的计数值,一个BIGINT的值。 若是没有匹配到数据,COUNT(expr)将返回0,一般有下面这三种用法:缓存

一、COUNT(字段) 会统计该字段在表中出现的次数,忽略字段为null 的状况。即不统计字段为null 的记录。 并发

二、COUNT(*) 则不一样,它执行时返回检索到的行数的计数,无论这些行是否包含null值,app

三、COUNT(1)跟COUNT(*)相似,不将任何列是否null列入统计标准,仅用1表明代码行,因此在统计结果的时候,不会忽略列值为NULL的行。编辑器

因此执行如下数据会出现这样的结果(这边是故意给component字段设置了几个null值):函数

1 select COUNT(*),COUNT(1),COUNT(component) from worklog;

 

概括以下: 性能

count(*) 包括了全部的列,至关于行数,在统计结果的时候,不会忽略列值为NULL  
count(1) 包括了忽略全部列,用1表明代码行,在统计结果的时候,不会忽略列值为NULL  
count(字段) 只包括字段那一列,在统计结果的时候,会忽略列值为null的计数,即某个字段值为NULL时,不统计

 

关于 COUNT(*) 和 COUNT(1)

先看看COUNT(*),MyISAM 引擎会把一个表的总行数记录了下来,因此在执行 COUNT(*) 的时候会直接返回数量,执行效率很高。对于InnoDB这样的事务性存储引擎, 由于增长了版本控制(MVCC)的缘由,同时有多个事务访问数据而且有更新操做的时候,每一个事务须要维护本身的可见性,那么每一个事务查询到的行数也是不一样的,因此不能缓存具体的行数,他每次都须要 count 计算一下全部的行数。

至于 COUNT(1) 和 COUNT(*)有什么区别呢,根据官网的内容(即上述截图倒数第二段),两种实现上其实同样:

InnoDB handles SELECT COUNT(*) and SELECT COUNT(1) operations in the same way. There is no performance difference. 

由于COUNT(*) 不care返回值是否为空都会将改行归入计算,因此他count了全部行数,而 COUNT(1) 中的 1 ,则是遇到了行的时候为恒真表达式,因此 COUNT(*) 仍是 COUNT(1) 都是对全部的结果集进行 count,他们本质上没有什么区别。姑且认为 COUNT(*) ≈ COUNT(1)。 

 

关于COUNT(字段)

咱们再来看看的COUNT(字段),他的查询就简单粗暴了,就是进行全表扫描,而后判断拿到的字段的值是否是为NULL,不为NULL则累加。

相比COUNT(*),COUNT(字段)多了一个步骤就是判断所查询的字段是否为NULL,因此他的性能要比COUNT(*)和COUNT(1)慢。 

 

总结 

综上,COUNT(1)和 COUNT(*)表示的是直接查询符合条件的数据库表的行数。而COUNT(字段)表示的是查询符合条件的列的值,并判断不为NULL的行数的累计,效率天然会低一点,

除了查询获得结果集有区别以外,相比COUNT(1) 和 COUNT(字段)来说,COUNT(*)是SQL92定义的标准统计数的语法,是官方提供的标准方案,基于此,MySQL数据库对他进行过不少优化。

注:SQL92,是数据库的一个ANSI/ISO标准。它定义了一种语言(SQL)以及数据库的行为(事务、隔离级别等)。

下面是对一张具备3400W数据的表的统计过程,comid是整型,能够对比下执行效率差别: 

 

  

使用建议

根据总结的内容,从效率层面说,COUNT(*) ≈ COUNT(1) > COUNT(字段),又由于 COUNT(*)是SQL92定义的标准统计数的语法,咱们建议使用 COUNT(*)。

咱们再来看看MySQL数据库作了哪些优化:以MySQL中比较经常使用的执行引擎InnoDB和MyISAM为例子。

一、MyISAM不支持事务,MyISAM中的锁是表级锁;

由于MyISAM的锁是表级锁,因此同一张表上面的操做是串行执行的,MyISAM把表的总行数单独记录下来,若是只是使用COUNT(*)对表进行查询的时候,能够直接返回这个记录的数值就能够了。

这样表中总行数记录便可提供给COUNT(*)查询使用,又因MyISAM数据库是表级锁,数据库行数不会被并行修改,因此行数是准确无误的。

二、InnoDB支持事务,其中大部分操做都是行级锁。

这样就不能愉快的作这种缓存操做了,由于表的行数可能会被并发修改,缓存记录下来的总行数就不许确了。

在InnoDB中,使用COUNT(*)查询行数的时候,不须要进行扫表,只要获取记录行数而已。因此官方在针对InnoDB的 SELECT COUNT(*) FROM 语句执行过程,会自动选择一个成本较低的索引进行的话,这样就能够大大节省时间。

InnoDB中索引分为聚簇索引(主键索引)和非聚簇索引(非主键索引),聚簇索引的叶子节点中保存的是整行记录,而非聚簇索引的叶子节点中保存的是该行记录的主键的值,非聚簇索引要比聚簇索引小不少,MySQL会优先选择最小的非聚簇索引来扫表,这样能够保证COUNT(*)的最优效率。

当查询语句中包含WHERE以及GROUP BY条件,会有一些其余的因素影响,因此要综合考虑。

 

判断数据在否,COUNT怎么用?

上面那种很获取COUNT数的场景多用于数据分页,数据统计的场景,有不少的状况则是直接判断数据是否存在,这种状况下,实际上是不关心有多少数据。可是咱们CoreReview的时候仍是会很常常看到这种作法:

1 select COUNT(*) from test_ucsyncdetail where comid>520;
2 
3 int count = testDao.CountByComId(comId);
4 if(count>0){
5    //存在,则执行存在分支的代码
6 }
7 else{
8   //不存在,则执行存在分支的代码
9 }

更好的写法应该是这样:

1 select 1 from test_ucsyncdetail where comid>520 limit 1;
2  
3 Object tda= testDao.checkExit(comId);
4 if(tda != null){
5     //存在,则执行存在分支的代码
6 }
7  else{
8    //不存在,则执行存在分支的代码
9 }


规避了SQL使用COUNT表达式扫表的操做,而是改用SELECT 1 ... LIMIT 1,数据库查询时遇到一条就返回,不会再继续查找和执行,若是存在传输回一条结果为1的数据 ,不然为null,业务代码中直接判断是否非空便可

 

后记

细节把握的好很差,真的影响很大,接下来准备从新撸一下 《高性能MySQL》和《MySql笔记》。

相关文章
相关标签/搜索