mysql的count统计原理

发布时间 2023-11-11 11:15:35作者: 刘百会

在我们平时查询数据库表记录行数的时候,经常会使用到count()函数,比如使用count(*)、count(1)或者count(某个主键或索引列),今天我们来对比下这些用法中哪个性能最优秀!

count()方法的目的是计算当前sql语句查询得到的非NULL的行数 。

我们知道mysql是分为server层和存储引擎层的 。

 

存储引擎层里可以选择各种引擎进行存储,最常见的是innodb、myisam。具体使用哪个存储引擎,可以通过建表sql里的ENGINE字段进行指定。比如这篇文章开头的建表sql里用了ENGINE=InnoDB,那这张表用的就是innodb引擎。

虽然在server层都叫count()方法,但在不同的存储引擎下,它们的实现方式是有区别的。

比如同样是读全表数据 select count(*) from sms;语句。

使用 myisam引擎 的数据表里有个记录当前表里有几行数据的字段,直接读这个字段返回就好了,因此速度快得飞起。

而使用innodb引擎 的数据表,则会选择体积最小的索引树 ,然后通过遍历叶子节点的个数挨个加起来,这样也能得到全表数据。

因此回到文章开头的问题里,当数据表行数变大后,单次count就需要扫描大量的数据 ,因此很可能就会出现超时报错。

那么问题就来了。

为什么innodb不能像myisam那样实现count()方法

myisam和innodb这两个引擎,有几个比较明显的区别,这个是八股文常考了。

其中最大的区别在于myisam不支持事务,而innodb支持事务。

而事务,有四层隔离级别,其中默认隔离级别就是可重复读隔离级别(RR) 

count()的括号里,可以放各种奇奇怪怪的东西,想必大家应该看过,比如放个星号*,放个1,放个索引列啥的。

我们来分析下他们的执行流程。

count方法的大原则是server层会从innodb存储引擎里读来一行行数据,并且只累计非null的值 。但这个过程,根据count()方法括号内的传参,有略有不同。

count(*)

server层拿到innodb返回的行数据,不对里面的行数据做任何解析和判断 ,默认取出的值肯定都不是null,直接行数+1。

count(1)

server层拿到innodb返回的行数据,每行放个1进去,默认不可能为null,直接行数+1.

count(某个列字段)

由于指明了要count某个字段,innodb在取数据的时候,会把这个字段解析出来 返回给server层,所以会比count(1)和count(*)多了个解析字段出来的流程。

  • 如果这个列字段是主键id ,主键是不可能为null的,所以server层也不用判断是否为null,innodb每返回一行,行数结果就+1.
  • 如果这个列是普通索引字段 ,innodb一般会走普通索引 ,每返回一行数据,server层就会判断这个字段是否为null,不是null的情况下+1。当然如果建表sql里字段定义为not null的话,那就不用做这一步判断直接+1。
  • 如果这个列没有加过索引 ,那innodb可能会全表扫描,返回的每一行数据,server层都会判断这个字段是否为null,不是null的情况下+1。同上面的情况一样,字段加了not null也就省下这一步判断了。

理解了原理后我们大概可以知道他们的性能排序是

count(*) ≈ count(1) > count(主键id) > count(普通索引列) > count(未加索引列)

所以说count(*),已经是最快的了。

知道真相的我眼泪掉下来。