14 - count(*)怎么优化

2020-06-07  本文已影响0人  天命_风流

关键字

count(*)、计算表行数

0.引子

在很多时候,你需要计算一个表的行数,比如计算一个交易系统中所有的变更记录的综述。一般情况下,我们使用一条简单的 select count(*) from t 的语句就可以解决。

但是,如果系统中的记录数越来越多,这个语句的执行也会越来越慢。这个问题看似很好解决,但是在不同的引擎下有不同的实现方式,

1.count(*)的实现方式

为什么 InnoDB 不将数据行数直接存储呢?主要是因为它支持多版本并发控制(MVCC),且 InnoDB 中的默认事务隔离级别为可重复读,所以即使在同一时刻,它应当返回多少行也是不确定的。我们可以用下面的例子解释:

假设表 t 中现在有 10000 条记录,我们设计了三个用户并行的会话。

我们假设从上到下是按照时间顺序执行的,同一行语句是在同一时刻执行的,它的执行结果如下: 14-并行流程.png

可以发现,三个会话拿到了不同的结果。在这里,有几点需要注意:

到了这里,你会发现,使用 InnoDB 似乎只能自己计数,那么该如何选择计数方法呢?

2.计数方法

2.1使用缓存计数

使用缓存计数应该是首先想到的方法,它比较简单,但是也会有一些问题(以 redis 为例):

你会发现,由于无法精确控制不同线程的执行时刻,所以不论怎样设计计数逻辑,它都是不够精确的。

2.2使用数据库保存计数

第二种解决方法,是将计数值单独存放在一个表中。那我们看一看,对于之前 redis 面临的问题,在这个方法下能否解决呢?

你会发现,即使会话 B 在 T3 时刻执行,但是由于 会话A 并没有提交,因此它不会看到计数值 +1 的操作。此时,它读出的计数值 和 最近 100 条记录在逻辑上就是一直的了。

了解了计数的办法,我们还可以稍微看一下不同 count 的性能差别。

3.不同 count 的用法

对于 count 一般我们有几种用法:count(*),count(主键 id),count(字段),count(1)等。我们来看一下与之相关的定义,以及不同 count 的性能(依然在 InnoDB 引擎下):

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

总结

上期问题

上期的问题是,什么时候使用 alter table t engine=InnoDB 会让一个表占用的空间反而变大。

答案是,如果这个表的本身就没有空洞,再做一次重建操作。由于 InnoDB 会在建表的时候预留一部分空间(六分之一),所以加上预留空间,这个表占用的空间反而变大了。

本期思考

在刚刚讨论的方案中,我们用了事务来确保计数准确。由于事务可以保证中间结果不被别的事务读到,因此修改计数值和插入新记录的顺序是不影响逻辑结果的。但是,从并发系统性能的角度考虑,你觉得在这个事务序列里,应该先插入操作记录,还是应该先更新计数表呢?


以上就是全部内容,希望你可以找到最优解。

注:本文章的主要内容来自我对极客时间app的《MySQL实战45讲》专栏的总结,我使用了大量的原文、代码和截图,如果想要了解具体内容,可以前往极客时间

上一篇 下一篇

猜你喜欢

热点阅读