MySQL:SELECT COUNT 小结

背景

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

讨论归纳

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

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

大概可以分下面这几个步骤讨论。

COUNT(expr)的分析

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

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

2、COUNT(*) 则不同,它执行时返回检索到的行数的计数,不管这些行是否包含null值,

3、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为例子。

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

因为MyISAM的锁是表级锁,所以同一张表上面的操作是串行执行的,MyISAM把表的总行数单独记录下来,如果只是使用COUNT(*)对表进行查询的时候,可以直接返回这个记录的数值就可以了。

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

2、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笔记》。

MySQL:SELECT COUNT 小结的更多相关文章

  1. mysql select count(filed) 问题(where条件没有数据匹配的话也有数据返回)。

    问题: SELECT count(*),user_id FROM tb_rp_logintrace WHERE id=-1 返回结果: count(*), user_id 0             ...

  2. mysql SELECT FOUND_ROWS()与COUNT(*)用法区别

    在mysql中 FOUND_ROWS()与COUNT(*)都可以统计记录,如果都一样为什么会有两个这样的函数呢,下面我来介绍SELECT FOUND_ROWS()与COUNT(*)用法区别   SEL ...

  3. php学习之道:mysql SELECT FOUND_ROWS()与COUNT(*)使用方法差别

    在mysql中 FOUND_ROWS()与COUNT(*)都能够统计记录.假设都一样为什么会有两个这种函数呢.以下我来介绍SELECT FOUND_ROWS()与COUNT(*)使用方法差别 SELE ...

  4. MySql的count统计结果

    起因:最近在学习mysql的数据库,发现在innodb表中大数据量下count(*)的统计结果实在是太慢,所以想找个办法替代这种查询,下面分享一下我查找的过程. 实践:在给出具体的结论之前,我们先看看 ...

  5. MySQL的COUNT()函数理解

    MySQL的COUNT()函数理解 标签(空格分隔): MySQL5.7 COUNT()函数 探讨 写在前面的话 细心的朋友会在平时工作和学习中,可以看到MySQL的COUNT()函数有多种不同的参数 ...

  6. select count(*) from user注入

    先来看一条sql语句: mysql; +------+----------+----------+------------+ | id | username | password | flag | + ...

  7. 从多表连接后的select count(*)看待SQL优化

    从多表连接后的select count(*)看待SQL优化 一朋友问我,以下这SQL能直接改写成select count(*) from a吗? SELECT COUNT(*) FROM a LEFT ...

  8. MySQL Select查询

    1. 基本语法: SELECT {* | <字段列名>} [ FROM <表 1>, <表 2>… [WHERE <表达式> [GROUP BY < ...

  9. mysql技巧之select count的比较

        在工作过程中,时不时会有开发咨询几种select count()的区别,我总会告诉他们使用select count(*) 就好.下文我会展示几种sql的执行计划来说明为啥是这样.   1.测试 ...

随机推荐

  1. Composer 包版本的范围指定(版本约束)

    1. 包版本范围指定(自动下载版本约束范围中的最新版) 名称 实例 说明 不指定版本 根据当前Path环境变量中的php版本下载最合适的最新版 确切的版本 6.0.1 指定下载的具体版本号 范围 &g ...

  2. django.db.utils.InternalError: (1091, "Can't DROP 'cre_time'; check that column/key exists")

    在执行命令python manage.py migrate时报错:django.db.utils.InternalError: (1091, “Can’t DROP ‘cre_time’; check ...

  3. STL(常用)

    STL 简单记录.讲解一些初级阶段常用的用法. STL是C++的一个标准模板库,其中包含了许多在计算机领域常用的基本数据结构以及基本算法.STL主要依赖于模板,使得STL具有广泛的通用性.这篇文章旨在 ...

  4. command三国杀开发日记20200915

    一句话进展 完善了程序结构,分离.c和.h 搭建了6个阶段函数 实现了玩家摸牌 封装实现了日志打印函数 日志打印 想要区分日志等级,包括DEBUG.INFO.WARN.ERRRO.PANIC,提供统一 ...

  5. 软件工程与UML作业3(互评作业)

    博客班级 https://edu.cnblogs.com/campus/fzzcxy/2018SE1/ 作业要求 https://edu.cnblogs.com/campus/fzzcxy/2018S ...

  6. Sqlmap爆库命令的简单使用

    转载:https://blog.csdn.net/qq_41617034/article/details/89502428 测试地址:因为涉及商业隐私,这里就不列啦 我这里用的是windows,并没有 ...

  7. Java Set 常用集合 HashSet、LinkedHashSet、TreeSet

    Java 中的 Set 是非常常用的数据类型.Set 是无序的 Collection,Java Set 有三个常用的实现类,分别是:HashSet.LinkedHashSet.TreeSet 本文基于 ...

  8. 空间向量变换,以及OpenGL的glm库简单应用

    测试项目请查看GitHub库 GLBIproject2/GLBIProject2_2

  9. 码云+Git配置仓库

    版本库Git安装 概述 Git是一个开源的分布式控制系统,可以有效高速的处理从很小的到非常大的项目版本管理,是目前使用范围最广的版本管理工具. 下载安装 下载后傻瓜式一键安装,建议安装在英文目录下,安 ...

  10. 系统模块sys的常用方法

    sys.argv参数 sys.argv  等价于是一个列表,argv[0] 代表当前脚本程序路径名,可以通过在外部获取多个参数传递给脚本程序执行,按照列表的取值方式第二个数据就是sys.argv[1] ...