1. Select count(*) 为什么会比较慢

在不同的 MySQL 引擎中,count(*) 有不同的实现方式。

MyISAM 引擎

  1. MyISAM 引擎把一个表的总行数存在了磁盘上,因此执行 count(*) 的时候会直接返回这个数,效率很高;

InnoDB 引擎

  1. 而 InnoDB 引擎就麻烦了,它执行 count() 的时候,需要把数据一行一行地从引擎里面读出来,然后累积计数。 此外需要注意的是,我们在这篇文章里讨论的是没有过滤条件的 count()。

但是如果加了 where 条件的话,MyISAM 表也是不能返回得这么快的(因为有条件,也需要把数据读出来进行判断)

2.为什么 InnoDB 不跟 MyISAM 一样,也把数字存起来呢

因为即使是在同一个时刻的多个查询,由于多版本并发控制(MVCC)的原因,InnoDB 表“应该返回多少行”也是不确定的。
在这里插入图片描述

会话 A 先启动事务并查询一次表的总行数;
会话 B 启动事务,插入一行后记录后,查询表的总行数;
会话 C 先启动一个单独的语句,插入一行记录后,查询表的总行数。

你会看到,在最后一个时刻,三个会话 A、B、C 会同时查询表 t 的总行数,但拿到的结果却不同。

这和 InnoDB 的事务设计有关系,可重复读是它默认的隔离级别。在代码上就是通过多版本并发控制,也就是 MVCC 来实现的。

所以为什么MyIsam可以直接记录一个总数,因为MyIASM压根不支持事务,也不支持MVCC,所有线程看到的记录都是一样的,当然可以直接记录一个总数,需要的时候再读。

但是InnoDB就不能够这样,对于每一个事务来说,它都有自己可见的记录数,跟自己当前事务版本号有关,所以就需要一行一行的统计记录数。

3. count(*)这么慢,MySQL没有进行优化吗

其实MySQL是做了优化的,因为InnoDB 是索引组织表,主键索引树的叶子节点是数据,而普通索引树的叶子节点是主键值。所以,普通索引树比主键索引树小很多。因为其叶子结点只存放主键信息,其数据页的行密度比较大,最终扫描的数据页较少,节省了IO开销

对于 count(*) 这样的操作,遍历哪个索引树得到的结果逻辑上都是一样的。因此MySQL 优化器会找到最小的那棵树来遍历。在保证逻辑正确的前提下,尽量减少扫描的数据量,是数据库系统设计的通用法则之一。

4.count(*)数据多了很慢怎么解决

1. 使用redis进行计数
在每次新增数据时,就在redis当中进行加1,每删除一行就减1。理想上是可以的,但是也会存在问题

问题一:如果redis荡机,异常重启怎么办?
reids重启了,可能会导致数据进行丢失。但是我们可以在重启过程中,在去数据库查询一遍总数进行存储,这样也是可以的。

问题二:高并发情况下,怎么保证一致性
比如在高并发下,两个线程同时进行,就会出现不一致的问题。

1.这里主要原因是因为“MySQL插入一行数据”跟“Redis计数加1”这两个操作是分开的,不是原子性的,这就很可能在中间过程因为某些并发出现问题。

2.更抽象一点:MySQL和Redis是两个不同的载体,将关联数据记录到不同的载体,而不同载体要实现原子性很难,由于不是原子性很容易引起并发问题。

3.如果能将数据统一在同个载体即MySQL,并由其保证操作的原子性,即将插入一行数据和计数加1作为一个完整的事务,通过事务的隔离此时外界看到的就是要么全部执行完毕要么全部都没执行,进而保持逻辑一致。

2.在数据库保存计数
把总数存储在另外一张表中,新增就+1,减少就-1

问题一:MySQL荡机了怎么办
因为MySQL有我们的binlog日志和redlog日志,他可以保证我们MySQL即使荡机了,数据也不会丢失。通过两阶段提交保证两个日志逻辑上的一致性。

问题二:高并发情况下,怎么保证一致性
将计数器的修改和数据的写表在一个事务中。读取计数器和查询最近数据也在一个事务中。因为在事务可重复读(repeatable read)中,别人改数据的事务已经提交,我在我的事务中也不去读。

可重复读的核心就是一致性读(consistent read);而事务更新数据的时候,只能用当前读。如果当前的记录的行锁被其他事务占用的话,就需要进入锁等待。

5. Select count(*) 、count(主键)、 count(字段)、count(1) 谁更快

count(主键 id)

InnoDB 引擎会遍历整张表,把每一行的 id 值都取出来,返回给 server 层。server 层拿到 id后。因为主键id判断是不可能为空的,就按行累加。

count(1)

InnoDB 引擎遍历整张表,但不取值。server 层对于返回的每一行,放一个数字“1”进去,判断是不可能为空的,按行累加。

count(字段)

如果这个“字段”是定义为 not null 的话,一行行地从记录里面读出这个字段,判断不能为 null,按行累加;

如果这个“字段”定义允许为 null,那么执行的时候,判断到有可能是 null,还要把值取出来再判断一下,不是 null 才累加。

count(*)

首先count()肯定是不为null的,所以他不用进行非空判断,并且MySQL已经对count()进行优化了。

总体性能排序
按照效率排序的话,count(字段)<count(主键id)<count(1) 约等于 count(*)。因为mysql对count(*)有优化,认为是取行数,不需要把字段取出来

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐