MySQL中count(*)函数原理详解

COUNT() 函数,是我们在平常的开发工作中,经常会用到的一个函数,它可以用来统计某个列值的数量,也可以用来统计行数。

count(*) 为例,我们先介绍一下它在 MySQL 中的实现方式。

MyISAM 引擎 和 InnoDB 引擎中 count(*) 的实现方式

select count(*) from t 这条语句为例(注意这里不带任何的 where 条件)。

  • MyISAM 引擎 中,每个表的总行数都会在内存和磁盘文件中进行保存,内存中的 count 变量值通过读取文件中的 count 值来进行初始化。当执行 count(*) 语句的时候,会直接将内存中保存的数值返回,所以执行非常快。
  • 而在InnoDB 引擎中,当执行 count(*) 的时候,它需要一行一行的进行统计和计数,并将最终的统计结果返回。

也就是说,MyISAM 引擎中 count(*) 的时间复杂度是 O(1),InnoDB 引擎中 count(*) 的时间复杂度是O(N)。

所以随着表中数据越来越多,使用InnoDB 引擎的表,这条语句执行得也会越来越慢。

那为什么 InnoDB 引擎就不能像 MyISAM 引擎一样,也把总行数保存到内存和磁盘文件中呢?

这是因为 InnoDB 引擎实现了多版本并发控制(MVCC)的原因:对同一个表,不同事物在同一时刻,看到的数据可能是不一样的。

我们拿个例子来说明下。

假设我们用的 InnoDB 引擎中事物隔离级别为默认的可重复读(repeatable-read)。表 t 中现有10条数据,这时又来了三个并发请求。

会话A 会话B 会话C
begin;
select count(*) from t;
返回 10
insert into t…
begin;
insert into t…
select count(*) from t;
返回 10
select count(*) from t;
返回 12
select count(*) from t;
返回 11

可以看到,在最后一个时刻,会话 A、B、C 同时查询表 t 的总行数,但拿到的结果却不同。这也就是为什么 InnoDB 引擎,无法像MyISAM 引擎那样,将表的总行数进行保存,因为它无法提供一个统一的值。

刚上边介绍的时候,我们着重强调了一下,不能带任何的 where 条件,如果加了 where 条件的话,MyISAM 引擎和其它引擎一样,也是不能返回的这么快的。

InnoDB 引擎对 count(*) 的优化

InnoDB 针对 count(*) 语句是做了些优化的,count(*)的目的只是为了统计总行数,它根本不关心自己查到的具体值,所以,InnoDB引擎在统计计数的时候,它会选择一个成本较低的索引树进行扫描,尽量避免扫描行数据,从而大大节省时间。

使用汇总表统计计数

既然 InnoDB 引擎中无法给我们记录总条数,那我们可以自己定义一张汇总表,当插入一条新记录时,我们就将汇总表中记录的值加1。

假设表 t 中还是有10条记录,此外有一张汇总表表 c,表 c 中记录值为10。这时来了如下几个并发请求。

会话A 会话B 会话C 会话D
begin;
表c中记录值加1
begin;
读表c中记录的值
(返回10)
读表t中数据(有10条)
commit;
begin;
读表c中记录的值
(返回10)
表 t 中插入一行数据
commit;
读表t中数据(有10条)
commit;
begin;
读表c中记录的值
(返回11)
读表t中数据(有11条)
commit;

这样,我们就可以快速的获取总条数了。还有,细心的同学可能已经发现,上边的几个会话都开启了事务。这里也是为了保证我们查出来的表c中的数值,和表t中的条数始终一致。

如果都不用事务的话,会话B中查出来的表c中的值将会是11,而表t中却只有10条数据。而如果只有会话A里的更新和插入操作加事务的话,会话C中查出来的表c中的值将会是10,而表t中却有11条数据。

count(*)、count(1)、count(列名)之间的区别

count()可以统计行数,也可以统计某个列值的数量。如果指定了列,则统计时要求列值是非空的(不为NULL),也就是只会统计有值的结果数。

count(*): 用于统计行数。这里的通配符 "*" 并不会扩展成所有的列,实际上它会忽略所有的列,直接统计所有的行数。

count(1): 用于统计行数。按照MySQL官方文档的介绍,count(1) 跟 count(*) 没有区别。

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

count(列名): MySQL会将列值取出来判断是否为NULL,如果不为NULL,则进行累加,最终统计的是指定列有值的结果数。

如果我们需要统计行数,建议使用 count(*) ,count(*) 是SQL92定义的标准统计行数的语法,跟数据库无关,跟NULL和非NULL无关。

你可能感兴趣的:(MySQL,MySQL中count函数,count函数原理,count函数优化,count用法,count函数区别)