【MySQL笔记】梳理下COUNT(*),COUNT(1),COUNT(字段名)区别、查询过程及常见问题


今天复盘了以下这个经典面试问题,做个记录

稍微介绍下COUNT

COUNT(expr):

  • 返回SELECT语句查询的行中expr不为NULL的数量

  • 结果是一个BIGINT

    • 顺便回顾下几个INT的区别

      类型大小有符号范围无符号范围
      TINYINT1 Bytes(-2^4 , 2^4-1)(0, 2^8)
      SMALLINT2 Bytes(-2^15, 2^15-1)(0, 2^16)
      MEDIUMINT3 Bytes(-2^23, 2^23-1)(0, 2^24)
      INT/INTEGER4 Bytes(-2^31, 2^31-1)(0, 2^32)
      BIGINT8 Bytes(-2^63, 2^63-1)(0, 2^64)
  • 如果查询没有任何结果,返回0

COUNT(字段名)COUNT(*)COUNT(1)

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

后面就是遍历通过count+1来计数,三种COUNT()方式的不同就是在这个过程中。

COUNT(字段名)

先说结果:查询符合条件,并且字段名不为NULL的行数

查询过程

大概过程就是:

  • server 层会循环向 InnoDB 读取一条记录
  • 如果指定的字段名参数不为NULL,则count+1
  • 如果符合条件的行数被读完,推出循环,将count发回客户端

但是过程中

COUNT(主键)有什么不同?

需要注意,参数中的字段名为主键的时候,具体查询过程又会根据表中【是否有二级索引】有所区别。

因为B+树中:

  • 聚簇索引(主键索引)的叶子节点存放的是实际数据;
  • 二级索引的叶子节点存放的是主键值,而不是实际数据。

可见,相同数量的二级索引记录比主键索引占用更少的存储空间(二级索引的B+树也比主键索引的B+树小)。

同样是遍历,二级索引的I/O成本明显小于主键索引,而我们都知道InnoDB之所以采用B+树作为索引的数据结构,很大程度就是为了减少将磁盘读到内存的I/O次数。

所以,

如果表中有二级索引时,【优化器】会优先遍历二级索引来计数,没有二级索引才会遍历主键索引

COUNT(非索引字段)呢?

那么如果这个参数字段不是索引的时候呢?

前面说了,参数里的字段名是主键索引时,考虑到遍历I/O成本,会优先走二级索引遍历计数,咱当时就有个疑问,如果字段不是索引呢?

答案很简单,就不会走索引了哈哈哈,explain一下,可以看到下面的type=ALL,就是全表扫描了,当然这样性能会受影响,所以没有特殊需求,不建议这么做

在这里插入图片描述

COUNT(1)

先说结果:与COUNT(*)类似,直接查询符合条件的行数

查询过程

  • InnoDB 循环遍历聚簇索引(主键索引),但不会读取记录中的任何字段的值(因为1不是一个字段)
  • server 层每从 InnoDB 读取到一条记录,就将 count 变量加 1

COUNT(主键字段)类似:

  • 此时如果表中有二级索引会默认遍历二级索引计数,没有就遍历主键索引;
  • 如果表中有多个二级索引,会选用**key_len最小的二级索引**进行遍历扫描,并计数

但不同的是,COUNT(1)少了【判断当前行参数字段是否为NULL】这一步,因为现在的参数是1,不可能是NULL,所以效率会高一点

COUNT(*)

先说结果:查询符合条件的行数

需要注意一点,这里的*select *不太一样,不是读取记录中所有字段,而是0

即使用 count(*) 时,MySQL 会将 * 参数转化为参数 0 来处理,最后执行的是COUNT(0)

查询过程

因此,count(*) 执行过程跟 count(1) 执行过程基本一样的,性能也差不多

常见面试问题

下面来盘一下相关的面试问题

三个查询的结果有什么不同?

  • COUNT(字段名): 查询符合条件,并且字段名不为NULL的行数
  • COUNT(*)COUNT(1): 查询所有符合条件的行数

为什么不推荐使用COUNT(字段名)

  • 参数字段是索引,与COUNT(*)COUNT(1),会优先遍历key_len小的非聚簇索引。但,相比于COUNT(*)COUNT(1)COUNT(字段名)多了一步,即需要判断字段是否为NULL,效率会差一些;
  • 如果参数字段不是索引,则会走全表搜索,效率更差了。

COUNT(1)COUNT(*)之间有什么不同?效率哪个更高?

  • 不同:二者都是查询所有符合条件的行数,不需要判断字段是否为NULL,区别是COUNT(*)等于把参数换成0,即最后执行的是COUNT(0),因为01都不是NULL,所以查的是所有符合条件的行
  • 效率:都差不多,但建议使用count(*),因为这是SQL92定义的标准统计行数的语法

MySQL中,InnoDB和MyISAM对COUNT(*)有那些优化?

InnoDB与MyISAM的一些区别

先稍微回忆一下InnoDB与MyISAM的一些区别,比如:

  • 事务与锁:
    • MyISAM不支持事务,MyISAM中的锁是表级锁;
    • 而InnoDB支持事务,支持表级锁与行级锁。
  • 索引数据结构:
    • MyISAM支持多种索引数据结构,比如 B+ 树索引、R 树索引、Full-Text 索引,建表时默认的主键索引时B+树;
    • InnoDB索引结构只支持B+树索引;
  • 索引中的叶子节点:
    • MyISAM 存储引擎:B+ 树索引的叶子节点保存数据的物理地址;
    • InnoDB 存储引擎:B+ 树索引的叶子节点保存数据本身;

InnoDB与MyISAM对COUNT(*)的优化

优化的前提是查询语句中不包含where条件和group by条件

先说结论:

  • MyISAM:会把表的总行数单独记录下来,如果执行count(*)时可以直接返回;
  • InnoDB:会优先选择最小的非聚簇索引来扫表。

那么就有几个问题:

  • 为什么InnoDB不像MyISAM那样存一个总行数?

    • 因为MyISAM是表级锁,不会有并发的行操作,所以查到的结果是准确的;而InnoDB支持事务,大部分事务都是行级锁(只有串行化才会上表级锁),行可能被并行修改,所以像MyISAM一样对总数做缓存不太安全
  • 为什么MyISAM不想InnoDB那样优先选最小的非聚簇索引来计数?

    • 因为MyISAM索引树的叶子节点存的是一个数据的物理地址,查询时都需要回表,等于MyISAM建立的索引全都是二级索引

SELECT COUNT(*) 的时候,加不加where条件有差别吗?

这部分很多帖子都只在问题里罗列下,好像也没解答

其实就是跟InnoDB优先走二级索引有关,前面也提到了”优化的前提是查询语句中不包含where条件和group by条件

稍微做个小实验:

-- actor_id 为主键索引,last_name 为二级索引,first_name不是索引
explain select count(*) from actor;
explain select count(*) from actor where actor_id > 3;
explain select count(*) from actor where first_name like 'P%';

结果如下:

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

可以看出:

加了where条件约束会使”优先遍历较小的二级索引计数“的优化失效,具体策略跟约束的字段有关:

  • 如果where的条件是一个主键索引的约束,会走主键索引
  • 如果where的条件不是索引,则会直接全表扫描

大表COUNT(*)时,如何优化?

当表中数据很庞大,即使各个引擎对COUNT(*)有优化,也很耗时,那么:

  • 如果不需要很精确的计数,可以通过explain 或者 show table status查一个近似值
  • 如果需要精确的计数,可以额外建个表存计数值(空间换时间,在给自己换点麻烦)

近似值

如果你的业务对于统计个数不需要很精确,可以使用 show table status 或者 explain 命令来表进行估算,执行 explain 命令效率是很高的,因为它并不会真正的去查询,结果中的rows只是一个估值

explain select count(*) from tb

(比如搜索引擎在搜索关键词的时候,给出的搜索结果条数是一个大概值)

额外建表保存计数值

字面意思,单独建张表存这个计数值,并在数据插入删除单独维护:

  • 有新数据插入时,将计数表中的计数字段 + 1;

  • 有数据删除时,将计数表中的计数字段 - 1

Reference

MyISAM中的索引方案
MySQL学习笔记:count(1)、count(*)、count(字段)的区别
count(*) 和 count(1) 有什么区别?哪个性能最好?

根据引用\[1\]和引用\[2\]的内容,count函数可以根据某个字段进行统计。具体来说,count(*)会统计所有行,包括NULL行;count(1)也会统计所有行,包括NULL行;而count(column)则只会统计column中非NULL的值。在执行效率上,MySQL会对count(*)进行优化,具体优化方式取决于列是否为主键。如果列为主键,count(列名)的效率优于count(1);如果列不是主键,count(1)的效率优于count(列名)。如果表中存在主键,count(主键列名)的效率最优;如果表中只有一列,则count(*)的效率最优;如果表有多列且不存在主键,则count(1)的效率优于count(*)。所以,根据不同情况,可以选择不同的count函数来进行统计。引用\[3\]中提到的max和min函数可以用于返回满足where条件的一列的最大值和最小值。例如,可以使用SELECT MAX(column) FROM table来求出某个表中某一列的最大值。 #### 引用[.reference_title] - *1* *2* [MySQLcount(1),count(*),count(某字段)等用法联系和区别](https://blog.csdn.net/weixin_43899069/article/details/120507754)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [Mysql数据库(十)| 合计/统计函数count](https://blog.csdn.net/weixin_45904051/article/details/121626974)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值