我就执行一个count(*)为什么这么慢?

前言

今天想看一下我的活动有多少用户参与了,随手执行了一条语句select count(*) from table;我等待几秒钟才出现结果,我不禁陷入了对社会和人生的大思考。

是我太菜吗?不这条语句简单到无法优化,以至于少一个符号就可能报错;是MySQL太笨了吗?用一个变量保存一个总数,每次要查的时候直接返回,不就好了吗?

上面的是真实的故事背景,我们今天就来聊一聊count(*)底层到到底是怎么实现的。

count的实现方式

需要明确的是,不同的存储引擎中count(*)的实现方式都不相同

MyISAM:比较老的MyISAM存储引擎比较方便,使用了一个变量保存表的总行数。因此执行count(*)的时候会直接返回这个变量,效率很高。但是加了where条件的话就无法返回这么快了。

InnoDB:我上面前言里面举的例子就是在InnoDB下的,可能你感觉出来了。它执行count(*)的时候,需要把一行一行的从引擎里读出来,然后进行累计。

现在我们的表基本上都默认使用了InnoDB,所以这就造成了当数据越来越多的时候,计算一个表的行数要花的时间越来越长。

当然上面的这两个实现都是在没有where条件下的,没有where条件MyISAM会立即返回。但是如果加了where条件的话,MyISAM表也不能这么快返回了。

为什么InnoDB不学MyISAM把数字存起来

你可能会问:我们都说InnoDB有多么多么的好,但是它为什么不学MyISAM一样把表的行数存起来?

这其实还要归结到InnoDB的底层实现,我们知道InnoDB采用了MVCC多版本并发控制。一行数据可能有多个版本,有的事务中它是存在的,有的事务中它又被删除了。这就导致了即使在同一时刻,不同事务读取到的行数不相同。

所以InnoDB存储引擎也不知道这个时刻应该返回多少行数据,那么就只有一行一行的统计了。

看起来虽然笨,但是InnoDB也做了一些优化。我们知道InnoDB是索引组织表,逐渐索引树的叶子节点就是数据,而普通索引的叶子节点是主键值。所以说普通索引树比逐渐索引树所占内存就小很多。对于count(*)这种操作,无论遍历哪个索引树得到的结果都是一样的,因此我们如果遍历最小的那一颗树,这样减少了IO消耗,也会带来一定的性能提升。

count(1)和count(*)哪个更快?

这个问题经常看到面试题上有。count()是一个聚合函数,用于返回结果集,一行一行的统计判断后返回累计值。

count(*)、count(id)和count(1)都表示返回满足条件的总行数;count(字段)表示返回字段不为NULL的总个数。这两个语法上还是有区别的。

Count(主键id)

InnoDB会遍历整张表,把每一行id值取出来,返回给Server层。server拿到id后,发现不可能为空,就按行累加。

Count(1)

InnoDB遍历整整表,但不取值。server层对于返回的每一行,放一个数字占位符“1”进去,也是因为不为空,就按行累加。

从这两个来看,count(1)的执行比count(主键id)要快,因为从引擎返回id会设计解析数据行,以及拷贝字段值等操作。而占位符“1”没有这些操作。

Count(字段)

1:如果这个“字段”设置了not null,只需要一行一行的读出来然后累计
2:如果这个“字段”允许为null,那么执行的时候就需要多一步是否为null的判断,然后累加。

Count(*)

如果使用count(*),那么肯定就不是null,直接按行累加。但是并不会把全部字段都取出来,而是专门做了优化,不取值。如果按照效率排名的话就是:count(字段)<count(主键id)<count(1)≈count(*),所以一般情况下我们直接使用count(*)就好了

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值