Skip to content

不同的 count 到底有何区别?

背景

在业务中,可能经常需要计算一个表的行数,我们应该怎么处理呢?最暴力的做法有 select count(*) from t,但是随着记录的增加,这条语句也会变得越来越慢。

count(*) 的实现方式

首先需要明确,在不同的存储引擎,count(*) 的实现原理不同。

  • MyISAM 会把一个表的总行数存在磁盘上,因此执行 count(*) 的时候会直接返回这个数,效率很高。但如果加了 where 条件,MyISAM 也需要遍历一遍
  • InnoDB 在执行 count(*) 的时候,需要把数据一行一行地从引擎里面读出来,然后累积计数

为什么 InnoDB 不跟 MyISAM 一样,也把数字存起来呢?

因为 InnoDB 也不知道“应该返回多少行”,由于 MVCC 的原因,同一时刻的多个查询,能够查到的数据不一定相同,每一行数据都需要判断自己是否对这个会话可见,对于 count(*) 来说,InnoDB 只能逐行比对,可见的行才能够被统计。

注意

MySQL 其实对 count(*) 作了一些优化,MySQL 优化器会选择最小的那棵索引树来遍历,尽量减少扫描的数据量

count(*) 这么慢,可以怎么解决呢?

说白了就是自己找一个地方把记录表的行数存起来

  1. 使用 Redis 来维护数据库的记录总数,但是缓存系统会存在丢失更新的问题,即使 Redis 正常工作,从逻辑上也是无法保证计数值是准确的。因为在并发系统中,我们无法精确控制不同线程的执行时刻
  2. 使用计数表,利用事务的特性来解决逻辑不一致的问题

不同 count 的用法

TIP

count() 是一个聚合函数,对于返回的结果集,一行一行判断,如果 count 函数的参数不是 NULL,累加值就加 1,否则不加。最后返回累计值。 按照效率排序的话,count(字段) < count(主键id) < count(1) ≈ count(*) ,建议尽量使用 count(*)

count(*)、count(主键id) 和 count(1) 都表示返回满足条件的结果集的总行数;

count(字段) 则表示返回满足条件的数据行里面,参数“字段”不为 NULL 的总个数。

count(1) 执行得要比 count(主键id) 快。因为从引擎返回 id 会涉及到解析数据行,以及拷贝字段值的操作。

1. count(主键id)

InnoDB 会遍历整张表,把每一条记录的 id 值取出来,返回给 server 层。 server 层拿到 id 后,判断是不可能为空的,按行累加

2. count(1)

InnoDB 遍历整张表,但不取值。server 层对于返回的每一行,放一个数字 1 进去,判断是不可能为空的,按行累加

3. count(字段)

如果字段定义是 not null,一行行地从记录中取出这个字段,判断不可能为 null,按行累加。 如果字段定义允许为 null,在执行时判断有可能为 null,需要把值取出来再判断一下,不是 null 才累加。

4. count(*)

count(*) 不会把全部字段取出来,而是专门做了优化,不取值。 count(*) 肯定不是 null,按行累加

上次更新于: