当前位置: 首页 > 后端技术 > Java

纳尼?MySQL中的count(-)是否比count(1)快?

时间:2023-04-01 16:22:36 Java

今天有人跟我说,mysql中count(1)比count(*)快,这能忍吗?你必须和他打架。免责声明:以下讨论基于InnoDB存储引擎,MyISAM由于特殊情况会在文末单独讨论。先说结论:两者在性能上相差不大。1、实践中,我准备了一张表,有100W条数据。表结构如下:CREATETABLE`user`(`id`int(11)unsignedNOTNULLAUTO_INCREMENT,`username`varchar(255)DEFAULTNULL,`address`varchar(255)DEFAULTNULL,`password`varchar(255)DEFAULTNULL,PRIMARYKEY(`id`))引擎=InnoDBDEFAULTCHARSET=utf8mb4;如您所见,有一个主键索引。我们用两种方法来统计表中的记录条数,如下:可以看出,两条SQL的执行效率其实差不多,都是0.14s。再看另外两个统计:id是主键,username和address是普通字段。可见使用id进行统计也有一定的优势。宋哥这里是因为测试数据样本比较小,所以效果不明显。小伙伴们可以加大测试数据量,区别会更明显。那么造成这种差异的原因是什么,接下来我们就来简单分析一下。2.Explain分析我们先用explain来看一下这些SQL的不同执行计划:可以看到,前三种统计方式的执行计划是一样的,后两种也是一样的。这里跟大家讲解一下不同的项目:type:前三个type值是index,表示全索引扫描,也就是只遍历整个索引(注意索引不是整个表);最后两个type值是all,表示全表扫描,即不会使用索引。key:表示MySQL决定使用哪个索引来优化对表的访问,PRIMARY表示使用主键索引,NULL表示不使用索引。key_len:这个表示MySQL使用的key长度,因为我们的主键类型是INT,不是空的,所以取值为4。Extra:这里使用索引表示优化器只需要访问索引就可以得到需要的数据(不需要回表)。通过explain,我们其实可以大致看出,前三种统计方式的执行效率更高(因为使用了索引),而后两种统计方式的效率相对较低(没有使用索引,全表扫描)。仅仅上面的分析是不够的,我们从原理的角度来分析一下。3.原理分析3.1主键索引和普通索引在开始原理分析之前,先带大家看一下B+树,这对我们理解后面的内容很重要。大家都知道InnoDB中索引的存储结构是B+树(至于什么是B+树,它和B树有什么区别,本文不讨论,单独这两个就可以出一篇文章),主键index不同于普通索引的存储。下图是主键索引:可以看到,在主键索引中,叶子节点存储的是每一行的数据。在普通索引中,叶子节点存储主键值。当我们使用普通索引查找数据时,首先在叶子节点中查找主键,然后使用主键查找主键索引中的数据,相当于做了两次查找,即我们通常所说的回表操作。3.2原理分析不知道大家有没有注意到。我们在学习MySQL的时候,把count函数归到聚合函数的范畴,也就是avg、sum等,把count函数和这些归为一类,说明它也是聚合函数。功能。既然是聚合函数,就需要对返回的结果集逐行判断。这就涉及到一个问题,返回的结果是什么?我们分别来看:Forselectcount(1)fromuser;对于这个查询,InnoDB引擎会找一个最小的索引树来遍历(不一定是主键索引),但是不会读取数据,而是读取一个叶子节点返回1,最后累加结果。从用户中选择计数(id);对于这个查询,InnoDB引擎会遍历整个主键索引,然后读取id返回,但是因为id是主键,在B+树的叶子节点上,所以这个过程不会涉及到它的时候谈到随机IO(不需要回表等操作去数据页取数据),性能也还可以。从用户中选择计数(用户名);对于这个查询,InnoDB引擎会遍历整个表做全表扫描,读取每一行的username字段并返回,如果username在定义时设置为notnull,则直接统计username个数;如果用户名在定义的时候没有设置为notnull,那么先判断用户名是否为空,再进行统计。最后说一下selectcount(*)fromuser;这个SQL的特别之处在于它已经被MySQL优化过。MySQL看到count(*)就知道你要统计记录总数,它会找一个最小的索引树来遍历,然后统计记录数。因为主键索引(聚簇索引)的叶子节点是数据,而普通索引的叶子节点是主键值,所以普通索引的索引树更小。但是,在上面的例子中,我们只有主键索引,所以最终还是使用了主键索引。现在,如果我修改上面的表,给username字段加一个索引,那么我们看看explainselectcount(*)fromuser的执行计划;可以看到,此时使用的索引是用户名索引,这和我们之前分析的结果是一致的。从上面的描述我们可以看出第一个查询的性能最高,第二个是其次(因为需要读取id并返回),第三个是最差的(因为需要全表扫描),第四个是最糟糕的。查询性能接近第一个。4.MyISAM呢?可能有朋友知道selectcount(*)fromuser;在MyISAM引擎中运行是非常快的,因为MyISAM直接把表的行数存储在磁盘上,需要的时候直接读出来,所以非常快。MyISAM引擎之所以这样做,主要是因为它不支持事务,所以它的统计其实很容易,加一行记录就可以了。但是我们常用的InnoDB做不到这一点!为什么?因为InnoDB支持事务!InnoDB为了支持事务,引入了MVCC多版本并发控制,因此在读取数据时可能会出现脏读、幻读、不可重复读等问题。详情请参考https://www.bilibili.com/video/BV14L4y1B7mB视频。因此,InnoDB需要取出每一行数据来判断该行数据是否对当前会话可见。如果可见,则统计该行数据,否则不统计。当然,MySQL中的MVCC其实是一个非常宏大的话题。以后宋哥有空再给大家详细介绍一下MVCC。好了,现在大家明白了吗?如果您有任何问题,请留言讨论。