[MySQL]统计表的总行数,count(1)怎么优化

前言:

感谢mysql实战45讲,又学了很多~

最为重要用count(*)最好

1. count(*)实现方式?

MyISAM 引擎把一个表的总行数存在了磁盘上,因此执行 count(*) 的时候会直接返回这个数,效率很高(但是也是没有where条件的情况下);

InnoDB 引擎就麻烦了,它执行 count(*) 的时候,需要把数据一行一行地从引擎里面读出来,然后累积计数。

怎么InnoDB这么憨憨啊。

为什么不把数字也存起来呢?因为并发的原因。

InnoDB也是做了优化的,它会选取普通索引树种,最小的一棵树来便利。尽量减少了扫描量。

2. show table status?

[MySQL]统计表的总行数,count(1)怎么优化

 这个也有表多少行,跟count(*)比较呢?

但是这个也类似于mysql索引统计的扫描行数一样,是一种统计值。

感觉有点像抽样统计学。

ABLE_ROWS 就是从这个采样估算得来的,因此它也很不准。有多不准呢,官方文档说误差可能达到 40% 到 50%。所以,show table status 命令显示的行数也不能直接使用。

3. 解决办法

3.1 用缓存系统保存计数-redis(因为redis可能会重启,不可取)

redis 服务来保存这个表的总行数。这个表每被插入一行 Redis 计数就加 1,每被删除一行 Redis 计数就减 1。

但是缓存系统可能会失去更新,redis的数据不能持久的刘在内存中。

所以需要定期持久化的存储起来,但是仍然可能会失去更新

比如redis异常重启了。

重启后有可能丢失数据。

补救方式:

如果异常重启,那么从数据库里面执行一次count(*),然后写入redis。

但是,使用redis来计数,还是有问题的。

比如我们要分页,取100条数据,然后redis取出计数,

种是,查到的 100 行结果里面有最新插入记录,而 Redis 的计数里还没加 1

另一种是,查到的 100 行结果里没有最新插入的记录,而 Redis 的计数里已经加了 1。

说白了就是时序性,redis和mysql的写入有一个时差~

在并发系统里面,我们是无法精确控制不同线程的执行时刻

3.2 在数据库中保存计数

由于 InnoDB 要支持事务,从而导致 InnoDB 表不能把 count(*) 直接存起来,然后查询的时候直接返回形成的。

事务A 进行更新记录 ,记录表数值+1,新纪录插入 commit;

事务b进行查询,如果在事务A进行中,由于事务性,所以看得数据是一致的。

4. count(1) count(*) count(主键id)count(字段) 性能差异?

count() 是一个聚合函数,对于返回的结果集,一行行地判断,如果 count 函数的参数不是 NULL,累计值就加 1,否则不加。最后返回累计值。

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

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

count(1) 执行得要比 count(主键 id) 快。因为从引擎返回 id 会涉及到解析数据行,以及拷贝字段值的操作。

count(字段)还要判断一下为不为null,然后进行累加。为null不累加。

count(*)是例外,专门做了优化,不取值,count(*)肯定不是null,按行累加。

按照效率排序的话,count(字段)<count(主键 id)<count(1)≈count(*),所以尽量使用 count(*)。

5. 在事务序列里面,是先插入记录还是先更新总行数呢?(用一张表存放计数)

逻辑实现上是启动一个事务,执行两个语句:

insert into 数据表;

update 计数表,计数值加 1。

并发系统性能的角度考虑,应该先插入操作记录,再更新计数表。

因为之前看到的,行锁的竞争,先插入再更新能最大程度地减少事务之间的锁等待,提升并发度。

如果你的事务中需要锁多个行,要把最可能造成锁冲突、最可能影响并发度的锁的申请时机尽量往后放。

因为更新涉及到行锁竞争。