MySQL count(*)太慢怎么办

1、count(*)实现方式

MyISAM引擎把表的总行数存在磁盘上,在没有where条件的时候,如果执行count(*),会直接返回这个数。

InnoDB引擎在执行count(*)时,需要把数据一行一行地读出来,累积计数。这是由于InnoDB事务默认的隔离级别是可重复读,可重复读是通过多版本并发控制(MVCC)实现的,一个会话在执行count(*)的时候,要依次判断表中的每一行是否可见,可见的行进行累积计数。此外,InnoDB是索引组织表,普通索引树比主键索引树小很多,所以在进行count(*)时,优化器会寻找最小的那棵树来遍历。

2、解决方法:将计数保存在数据库中

把计数直接放到数据库中单独一张计数表中,当表中插入一行的时候,就计数表中的计数值就加1,当删除一行的时候,计数表中的计数值就减1。由于InnoDB是支持崩溃恢复数据的,因此这种做法不用担心因系统崩溃而导致的计数丢失。此外,InnoDB是支持事务的,默认的隔离级别是可重复读,因此对于可以保证数据的逻辑一致性。

如图可见,如果使用缓存保存计数,那么由于在并发系统中,我们不能控制不同线程的执行时刻,所以会出现计数值逻辑上不精确的情况。在T2时刻,会话A将计数加1,在T3时刻,会话B读计数时,计数已经加了1,但是却不能查到新加入的记录,因为这条记录在T4时刻才被会话A插入。而使用数据库保存计数就不会出现这种情况。
缓存系统保存计数
数据库表保存计数
并且,从并发性能的角度来看,我们应该先插入一行数据,再将计数加1,因为更新计数表设计到行锁的竞争,行锁在需要的时候加上,不需要不会立刻释放,而是要等到事务提交的时候才会释放,因此容易引起锁冲突的语句要尽量往后放。

3、不同count的性能

count(*)、count(主键)、count(1)表示返回满足条件的数据行的总行数;count(字段)表示返回满足条件的数据行里面,参数"字段"不为null的总行数。

count(主键):遍历整张表,把每一行的主键id值取出来,返回给server层,server层拿到id后,按行累加,不会忽略列值为null。

count(1):遍历整张表,但不取值,返回每一行给server层,用1代表代码行,按行累加,不会忽略列值为null。count(1)优于count(主键id),因为count(主键id)要额外做解析数据行,拷贝字段值的操作。

count(字段):只包括参数"字段"那一列,在统计结果的时候,会忽略字段值为null的计数。因此,需要做额外的判断字段是否为null的操作。

count(*):包括了所有的列,相当于行数,在统计结果的时候,不会忽略列值为NULL。MySQL对count(*)做了优化,最简单的执行办法是找一列定义为NOT NULL的列,如果该列有索引,则使用该索引,当然,为了性能,SQL Server会选择最窄的索引以减少IO。

按照效率,count(字段) < count(主键) < count(1)约等于count(*)。

猜你喜欢

转载自blog.csdn.net/Longstar_L/article/details/107372696