美文网首页
优化MySQL count(*)查询慢的问题

优化MySQL count(*)查询慢的问题

作者: cbhe | 来源:发表于2020-05-08 16:06 被阅读0次

为什么count(*)逻辑很简单但是查询起来却很慢

在开发中我们经常会遇到使用coun(*) 查询总数的需求。但是如果表中的数据过多的话,count(*)的执行时间会非常长,因此很有必要对其进行优化一下。

如果单纯的查一个表的记录总数的话

select count(*) from table t 

那么,一个简单的想法就是记下来表中的记录,并且在每次插入时加一,这样的话每次直接取出记录的值就行了,根本不会耗时。但想法很好,只是实现起来就没那么简单了。

如果是innodb引擎的话,因为要支持多版本并发控制MVCC,在每个事务中,即使同一时刻查询,那么得到的总数都是不一样的,因此简单存储起来记录总数的话,并不能满足即插即用的需求。例如表t中本来有1000条数据,那么根据图中的会话执行方式,同时在t6时刻查出来的记录数是不一样的。

图1 同一时刻不同事务查出的总条数不同

用缓存记录表行数

既然innodb无法为我们自动记录表行数,那么我们就需要自己实现一种方法克服事务并发问题,来记录表行数。一种可能的办法就是用缓存来记录一下每个表的行数。每次成功插入一条数据就将行数加一。不过缓存在内存中肯定不是一直能稳定访问的,当缓存丢失时,再执行一次count(*)就可以将表行数恢复了,因为缓存丢失也不是经常发生的,因此这种办法看起来也是可以接受的。

但实际上存到缓存(如redis)中依然无法满足我们的需求。每次我们需要知道表行数的时候,经常也会想要取出前10行记录。但由于向表中插入一条数据和对redis中保存的行数进行加一操作这两个步骤并不是原子性的,因此,如果正好在这两个步骤之间发起了一次查10条最新记录和查表行数的操作,则表行数和最新的10条记录就不一致了。

图2 最新记录与表行数不一致

用redis的问题在于我们无法在查询时锁定住表,使得我们获得的表行数和前10条的记录都是在查询时刻的数据。也就是说,我们只想要查到在查询时刻之前已经执行完“插入一条记录”和“redis行数+1”这两个步骤都执行完的会话的结果。仔细想一下,这不就是数据库事务的隔离属性吗?

在数据库中保存计数

我们将行数记录在innodb的另一个表c中,则在图2 的基础上我们得到而图3:

图3 完美解决一致性问题

这时,你会发现,我们将查行数和查10条数据放到一个事务里,将插入和累加也放到一个事物里,这样的话,在我们查询时,只能查到已经提交的事务的结果,行数和最新数据就一致了。

相关文章

网友评论

      本文标题:优化MySQL count(*)查询慢的问题

      本文链接:https://www.haomeiwen.com/subject/viitnhtx.html