当我们对一张数据表中的记录进行统计的时候,习惯都会使用 count 函数来统计,但是 count 函数传入的参数有很多种,比如 count(1)、count(*)、count(字段) 等。

到底哪种效率是最好的呢?是不是 count(*) 效率最差?

我曾经以为 count(*) 是效率最差的,因为认知上 selete * from t 会读取所有表中的字段,所以凡事带有 * 字符的就觉得会读取表中所有的字段,当时网上有很多博客也这么说。

但是,当我深入 count 函数的原理后,被啪啪啪的打脸了!

不多说, 发车!

哪种 count 性能最好?

哪种 count 性能最好?

我先直接说结论:

要弄明白这个,我们得要深入 count 的原理,以下内容基于常用的 innodb 存储引擎来说明。

count() 是什么?

count() 是一个聚合函数,函数的参数不仅可以是字段名,也可以是其他任意表达式,该函数作用是统计符合查询条件的记录中,函数指定的参数不为 NULL 的记录有多少个

假设 count() 函数的参数是字段名,如下:

select count(name) from t_order;

这条语句是统计「 t_order 表中,name 字段不为 NULL 的记录」有多少个。也就是说,如果某一条记录中的 name 字段的值为 NULL,则就不会被统计进去。

再来假设 count() 函数的参数是数字 1 这个表达式,如下:

select count(1) from t_order;

这条语句是统计「 t_order 表中,1 这个表达式不为 NULL 的记录」有多少个。

1 这个表达式就是单纯数字,它永远都不是 NULL,所以上面这条语句,其实是在统计 t_order 表中有多少个记录。

count(主键字段) 执行过程是怎样的?

在通过 count 函数统计有多少个记录时,MySQL 的 server 层会维护一个名叫 count 的变量。

server 层会循环向 InnoDB 读取一条记录,如果 count 函数指定的参数不为 NULL,那么就会将变量 count 加 1,直到符合查询的全部记录被读完,就退出循环。最后将 count 变量的值发送给客户端。

InnoDB 是通过 B+ 树来保持记录的,根据索引的类型又分为聚簇索引和二级索引,它们区别在于,聚簇索引的叶子节点存放的是实际数据,而二级索引的叶子节点存放的是主键值,而不是实际数据。

用下面这条语句作为例子:

//id 为主键值
select count(id) from t_order;

如果表里只有主键索引,没有二级索引时,那么,InnoDB 循环遍历聚簇索引,将读取到的记录返回给 server 层,然后读取记录中的 id 值,就会 id 值判断是否为 NULL,如果不为 NULL,就将 count 变量加 1。

但是,如果表里有二级索引时,InnoDB 循环遍历的对象就不是聚簇索引,而是二级索引。

这是因为相同数量的二级索引记录可以比聚簇索引记录占用更少的存储空间,所以二级索引树比聚簇索引树小,这样遍历二级索引的 I/O 成本比遍历聚簇索引的 I/O 成本小,因此「优化器」优先选择的是二级索引。

count(1) 执行过程是怎样的?

用下面这条语句作为例子:

select count(1) from t_order;

如果表里只有主键索引,没有二级索引时。

那么,InnoDB 循环遍历聚簇索引(主键索引),将读取到的记录返回给 server 层,但是不会读取记录中的任何字段的值,因为 count 函数的参数是 1,不是字段,所以不需要读取记录中的字段值。参数 1 很明显并不是 NULL,因此 server 层每从 InnoDB 读取到一条记录,就将 count 变量加 1。

可以看到,count(1) 相比 count(主键字段) 少一个步骤,就是不需要读取记录中的字段值,所以通常会说 count(1) 执行效率会比 count(主键字段) 高一点。

但是,如果表里有二级索引时,InnoDB 循环遍历的对象就二级索引了。

count(*) 执行过程是怎样的?

看到 * 这个字符的时候,是不是大家觉得是读取记录中的所有字段值?

对于 selete * 这条语句来说是这个意思,但是在 count(*) 中并不是这个意思。

count(\*) 其实等于 count(0),也就是说,当你使用 count(*) 时,MySQL 会将 * 参数转化为参数 0 来处理。

所以,count(*) 执行过程跟 count(1) 执行过程基本一样的,性能没有什么差异。

在 MySQL 5.7 的官方手册中有这么一句话:

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

翻译:InnoDB以相同的方式处理SELECT COUNT(\*)和SELECT COUNT(1)操作,没有性能差异。

而且 MySQL 会对 count(*) 和 count(1) 有个优化,如果有多个二级索引的时候,优化器会使用key_len 最小的二级索引进行扫描。

只有当没有二级索引的时候,才会采用主键索引来进行统计。

count(字段) 执行过程是怎样的?

count(字段) 的执行效率相比前面的 count(1)、 count(*)、 count(主键字段) 执行效率是最差的。

用下面这条语句作为例子:

//name不是索引,普通字段
select count(name) from t_order;

对于这个查询来说,会采用全表扫描的方式来计数,所以它的执行效率是比较差的。

小结

count(1)、 count(*)、 count(主键字段)在执行的时候,如果表里存在二级索引,优化器就会选择二级索引进行扫描。

所以,如果要执行 count(1)、 count(*)、 count(主键字段) 时,尽量在数据表上建立二级索引,这样优化器会自动采用 key_len 最小的二级索引进行扫描,相比于扫描主键索引效率会高一些。

再来,就是不要使用 count(字段) 来统计记录个数,因为它的效率是最差的,会采用全表扫描的方式来统计。如果你非要统计表中该字段不为 NULL 的记录个数,建议给这个字段建立一个二级索引。

为什么要通过遍历的方式来计数?

你可以会好奇,为什么 count 函数需要通过遍历的方式来统计记录个数?

我前面将的案例都是基于 Innodb 存储引擎来说明的,但是在 MyISAM 存储引擎里,执行 count 函数的方式是不一样的,通常在没有任何查询条件下的 count(*),MyISAM 的查询速度要明显快于 InnoDB。

使用 MyISAM 引擎时,执行 count 函数只需要 O(1 )复杂度,这是因为每张 MyISAM 的数据表都有一个 meta 信息有存储了row_count值,由表级锁保证一致性,所以直接读取 row_count 值就是 count 函数的执行结果。

而 InnoDB 存储引擎是支持事务的,同一个时刻的多个查询,由于多版本并发控制(MVCC)的原因,InnoDB 表“应该返回多少行”也是不确定的,所以无法像 MyISAM一样,只维护一个 row_count 变量。

举个例子,假设表 t_order 有 100 条记录,现在有两个会话并行以下语句:

在会话 A 和会话 B的最后一个时刻,同时查表 t_order 的记录总个数,可以发现,显示的结果是不一样的。所以,在使用 InnoDB 存储引擎时,就需要扫描表来统计具体的记录。

而当带上 where 条件语句之后,MyISAM 跟 InnoDB 就没有区别了,它们都需要扫描表来进行记录个数的统计。

如何优化 count(*)?

如果对一张大表经常用 count(*) 来做统计,其实是很不好的。

比如下面我这个案例,表 t_order 共有 1200+ 万条记录,我也创建了二级索引,但是执行一次 select count(*) from t_order 要花费差不多 5 秒!

面对大表的记录统计,我们有没有什么其他更好的办法呢?

*第一种,近似值*

如果你的业务对于统计个数不需要很精确,比如搜索引擎在搜索关键词的时候,给出的搜索结果条数是一个大概值。

这时,我们就可以使用 show table status 或者 explain 命令来表进行估算。

执行 explain 命令效率是很高的,因为它并不会真正的去查询,下图中的 rows 字段值就是 explain 命令对表 t_order 记录的估算值。

第二种,额外表保存计数值

如果是想精确的获取表的记录总数,我们可以将这个计数值保存到单独的一张计数表中。

当我们在数据表插入一条记录的同时,将计数表中的计数字段 + 1。也就是说,在新增和删除操作时,我们需要额外维护这个计数表。

MySQL 中 count(*) 和 count(1) 有什么区别?哪个性能最好?相关推荐

  1. MySQL中sum和count用法总结

    MySQL中sum和count用法总结 1.sum 2.count 例子:要求:查询出2门及2门以上不及格者的平均成绩. 3.MySQL 分组之后如何统计记录条数 gourp by 之后的 count ...

  2. MySQL中索引与视图的用法与区别详解

    索引与视图是我们在日常使用mysql必不可少的一部分,最近在学习中看到一本书中关于这方法写的不错,所以这篇文章主要给大家介绍了关于MySQL中索引与视图的使用与区别的相关资料,需要的朋友可以参考借鉴, ...

  3. mysql中的where和having子句的区别

    mysql中的where和having子句的区别 having的用法 having字句可以让我们筛选成组后的各种数据,where字句在聚合前先筛选记录,也就是说作用在group by和having字句 ...

  4. php普通索引和唯一索引,MySQL中普通索引和唯一索引的区别详解

    本篇文章介绍了MySQL中普通索引和唯一索引的区别,讲解很详细,希望对学习MySQL的朋友有帮助! 需要注意的是: redo log中的数据,可能还没有 flush 到磁盘,磁盘中的 Page 1 和 ...

  5. MySQL+create+base,MySQL中CREATE DATABASE和CREATE SCHEMA的区别

    MySQL中CREATE DATABASE和CREATE SCHEMA的区别 create schema 是创建模式 create database 是创建数据库 但进入MySQL不用选择任何数据库即 ...

  6. mysql中 REPLACE INTO 和 INSERT INTO 的区别

    mysql中 REPLACE INTO 和 INSERT INTO 的区别 REPLACE INTO 和 INSERT INTO 功能类似,都是像表中插入数据,不同点在于:REPLACE INTO 首 ...

  7. MySQL中char,varchar和text的区别

    MySQL中char,varchar和text的区别 java程序中的string类型,在mysql数据表中可以有三种对应的类型:char,varchar和text. char,varchar和tex ...

  8. MySQL中只会count(),sum()?累加运算没听过?

    点击蓝色"程序猿DD"关注我 回复"资源"获取独家整理的学习资料! 作者 | 码农小胖哥 来源 | 公众号「码农小胖哥」 01 前言 接了一个需求,产品想分析一 ...

  9. mysql中sum和count的区别

    sum()函数和count()函数的区别 其实两者很好容易区分,sum,即求和,多个列相加的和; count计数,一共有多少条数据 sum()函数求累加; 对符合条件的记录的数值列求和; count( ...

  10. MySQL中聚合函数count的使用和性能优化

    本文将探讨以下问题 1.count(*) . count(n).count(null)与count(fieldName) 2.distinct 与 count 连用 3.group by (多个字段) ...

最新文章

  1. jQuery / jQuery mvc plugin
  2. 重新格式化NameNode后,DataNode启动不起来问题解决
  3. ISA2004发布应用程序服务器
  4. 图形颜色填充_Processing-2-基本图形绘制
  5. 使用juery在iframe内部访问父页面元素
  6. 每天一道LeetCode-----给定二维数组代表海域和岛屿,计算有多少个孤岛
  7. github和dockerhub制作k8s镜像
  8. 【每日一题】8月4日题目精讲—购物
  9. 在 Angularjs 中 ui-sref 和 $state.go 如何传递参数
  10. Centos7中安装python3.7、pip3以及pipenv(亲测有效)
  11. 程序员作图工具和技巧,你 get 了么?
  12. rabbitmq安装erlang,报错configure: error: /bin/sh ‘/app/otp_src_20.2/erts/configure‘ failed for erts
  13. Hadoop大数据开发基础课后答案
  14. 中国卫生健康统计年鉴(2006-2021年)
  15. 通用 USB 重定向和客户端驱动器注意事项
  16. python继承封装多态简单理解_Python面向对象编程:封装、继承、多态(基础篇十)...
  17. HTML编程(网页的基本标签)
  18. GOCI数据批量下载
  19. 让Windows2000飞起来(转)
  20. openlayers在线地图:高德地图、天地图、谷歌、geoq(智图)

热门文章

  1. python将16进制字符串转换为整数_Python 16进制与字符串的转换
  2. redis 关系数据库怎么转换 和_redis数据库设计(转)
  3. 分阶段付款 学php,项目整理-支付宝的支付问题
  4. java system类_Java System类mapLibraryName()方法及示例
  5. java 根据类名示例化类_Java MathContext类| 带示例的getRoundingMode()方法
  6. math.pow int_Java Math类static int min(int i1,int i2)与示例
  7. sql服务器默认密码_搭建一个DNS服务器,轻松实现域名解析内容分发,访问速度提高N倍...
  8. ajax 跨站返回值,jquery ajax 跨域问题
  9. 的garch预测_随机森林预测
  10. 一文详解「队列」,手撸队列的3种方法!