共计 2634 个字符,预计需要花费 7 分钟才能阅读完成。
明天有人跟我讲 MySQL 中 count(1)
比 count(*)
快,这能忍?必须得和他掰扯掰扯。
申明:以下探讨基于 InnoDB 存储引擎,MyISAM 因为状况非凡我在文末会独自说一下。
先说论断:这两个性能差异不大。
1. 实际
我筹备了一张有 100W 条数据的表,表构造如下:
CREATE TABLE `user` (`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`username` varchar(255) DEFAULT NULL,
`address` varchar(255) DEFAULT NULL,
`password` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
能够看到,有一个主键索引。
咱们来用两种形式统计一下表中的记录数,如下:
能够看到,两条 SQL 的执行效率其实差不多,都是 0.14s。
再来看另外两个统计:
id 是主键,username 以及 address 则是一般字段。
能够看出,用 id 来统计,也有一丢丢劣势。松哥这里因为测试数据样板比拟小,所以成果不显著,小伙伴们能够加大测试数据量,那么这种差别会更加显著。
那么到底是什么起因造成的这种差别,接下来咱们就来简略剖析一下。
2. explain 剖析
咱们先用 explain 来看下这几个 SQL 不同的执行打算:
能够看到,前三个统计形式的执行打算是一样的,前面两个是一样的。
我这里和大家比拟下 explain 中的不同项:
- type:前三个的 type 值为 index,示意全索引扫描,就是把整个索引过一遍就行(留神是索引不是整个表 );后两个的 type 值为 all,示意 全表扫描,即不会应用索引。
- key:这个示意 MySQL 决定采纳哪个索引来优化对该表的拜访,PRIMARY 示意利用主键索引,NULL 示意不必索引。
- key_len:这个示意 MySQL 应用的键长度,因为咱们的主键类型是 INT 且非空,所以值为 4。
- Extra:这个中的 Using index 示意优化器只须要通过拜访索引就能够获取到须要的数据(不须要回表)。
通过 explain 咱们其实也能大略看进去前三种统计形式的执行效率是要高一些的(因为用到了索引),而前面两种的统计效率相对来说要低一些的(没用索引,须要全表扫描)。
仅有下面的剖析还不够,咱们再来从原理角度来剖析一下。
3. 原理剖析
3.1 主键索引与一般索引
在开始原理剖析以前,我想先率领大家看一下 B+ 树,这对于咱们了解接下来的内容有重要作用。
大家都晓得,InnoDB 中索引的存储构造都是 B+ 树(至于什么是 B+ 树,和 B 树有什么区别,这个本文就不探讨了,这两个独自都能整进去一篇文章),主键索引和一般索引的存储又有所不同,如下图示意主键索引:
能够看到,在主键索引中,叶子结点保留了每一行的数据。
而在一般索引中,叶子结点保留的是主键值,当咱们应用一般索引去搜寻数据的时候,先在叶子结点中找到主键,再拿着主键去主键索引中查找数据,相当于做了两次查找,这也就是咱们平时所说的 回表 操作。
3.2 原理剖析
不晓得小伙伴们有没有留神过,咱们学习 MySQL 的时候,count 函数是归在聚合函数那一类的,就是 avg、sum 等,count 函数和这些归在一起,阐明它也是一个聚合函数。
既然是聚合函数,那么就须要对返回的后果集进行一行行的判断,这里就波及到一个问题,返回的后果是啥?咱们别离来看:
对于 select count(1) from user;
这个查问来说,InnoDB 引擎会去找到一个最小的索引树去遍历(不肯定是主键索引),然而不会读取数据,而是读到一个叶子节点,就返回 1,最初将后果累加。
对于 select count(id) from user;
这个查问来说,InnoDB 引擎会遍历整个主键索引,而后读取 id 并返回,不过因为 id 是主键,就在 B+ 树的叶子节点上,所以这个过程不会波及到随机 IO(并不需要回表等操作去数据页拿数据),性能也是 OK 的。
对于 select count(username) from user;
这个查问来说,InnoDB 引擎会遍历整张表做全表扫描,读取每一行的 username 字段并返回,如果 username 在定义时候设置了 not null,那么间接统计 username 的个数;如果 username 在定义的时候没有设置 not null,那么就先判断一下 username 是否为空,而后再统计。
最初再来说说 select count(*) from user;
,这个 SQL 的非凡之处在于它被 MySQL 优化过,当 MySQL 看到 count(*)
就晓得你是想统计总记录数,就会去找到一个最小的索引树去遍历,而后统计记录数。
因为主键索引(汇集索引)的叶子节点是数据,而一般索引的叶子节点则是主键值,所以一般索引的索引树要小一些。然而在上文的案例中,咱们只有主键索引,所以最终应用的就是主键索引。
当初,如果我批改下面的表,为 username 字段也增加索引,而后咱们再来看 explain select count(*) from user;
的执行打算:
能够看到,此时应用的索引就是 username 索引了,和咱们后面的剖析后果是统一的。
从下面的形容中咱们就能够看出,第一个查问性能最高,第二个次之(因为须要读取 id 并返回),第三个最差(因为须要全表扫描),第四个的查问性能则靠近第一个。
4. MyISAM 呢?
可能有小伙伴晓得,MyISAM 引擎中的 select count(*) from user;
操作执行起来是十分快的,那是因为 MyISAM 把表中的行数间接存在磁盘中了,须要的时候间接读取进去就行了,所以十分快。
MyISAM 引擎之所以这样做,次要是因为它是不反对事务的,所以它的统计实际上就非常容易,增加一行记录一行就行了。
而咱们罕用的 InnoDB 却不能这样做!为啥?因为 InnoDB 反对事务!为了反对事务,InnoDB 引入了 MVCC 多版本并发管制,所以在数据读取的时候可能会有脏读、幻读以及不可反复读等问题,具体能够参考 https://www.bilibili.com/video/BV14L4y1B7mB 视频。
所以,InnoDB 须要将每一行数据拿进去,判断该行数据对以后会话是否可见,如果可见,就统计该行数据,否则不予统计。
当然,MySQL 中的 MVCC 实际上是一个十分巨大的话题,松哥当前有空了再和大家具体介绍 MVCC。
好啦,当初小伙伴们懂了吧?有问题欢送留言探讨。