count(*) 为何执行慢

随着表记录的增加,select count(*) from table 统计表信息的语句变的越来越慢了。

count(*)的实现方式

  • 在MyISAM引擎中, 表的总行数是实时更新存储在磁盘中。 因此这个统计的语句就十分快速, 但是如果统计语句加上了where条件,磁盘中存储的总行数就无效了, 就要实时累计计算了。
  • 在InnoDB引擎中, 执行这个统计语句的时候,就会扫描表的所有数据实时累计计算出总数来,这个过程自然很慢了

InnoDB为何不把总数记录到一个特定位置

这是由于它要支持事务的可重复读隔离级别, 使用了MVCC(多版本控制)的原因, 不同的事务使用不同的版本, 三个事务查询到的表的行数也会不一样的。 所以总行数无法保存下来。

这个就是RR隔离级别的代价,InnoDB也对这个统计语句进行了优化, 由于统计普通索引中总行数和统计主键索引的总行数 结果是一样的, 但是由于主键索引B+ tree的叶子节点还包含完整的行记录, 扫描主键索引计算总行数显然很不划算。 这个时候mysql 就会选择长度最小的索引来计算总行数。
在这里插入图片描述
图中所示就没有使用主键索引,而是使用了name字段的普通索引。

Show table status

这个命令也能显示出来表的总记录, 但是这里显示的总记录并不是正确的总记录, 它是使用采样统计的方式计算出来的, 和EXPLAIN命令中显示的扫描的行数的结果的计算方式一样的, 也是使用采样统计的,
在这里插入图片描述
可以看到显示的记录的行数是0 ,这是不对的,实际上这个表有三行数据的。

当发现统计的值相差太大的时候,可以使用Anaylze table t_user 命令来重新采样统计。 统计之后的值会比较准确一点。

使用optimize table、analyze table和alter table这三种方式重建表的区别。这里,我顺便再简单和你解释一下。

  • 从MySQL 5.6版本开始,alter table t engine = InnoDB(也就是recreate)默认的就是上面重建表的流程了;
  • analyze table t 其实不是重建表,只是对表的索引信息做重新统计,没有修改数据,这个过程中加了MDL读锁;
  • optimize table t 等于recreate+analyze。

如果有一个更新很频繁表,有需要频繁的显示实时的总行数,这个时候就需要我们自己来计数了。使用

缓存系统保存计数

使用redis 来计数,每次插入一行记录就redis中计数加一, 删除一条记录就减一。 redis的读和写都比较快速,但是容易出现丢失的情况,比如redis的淘汰策略导致这个数据淘汰了。 这个时候如果redis中没有值可以再次执行统计语句后把值放入redis中。

但是这个操作会出现数据不一致的问题, 插入数据库和更新redis 始终是两个操作,这两个操作中间时间段,就是redis 和mysql数据库中数据不一致的时间段。 如果在这个时间段有其他的线程获取到redis中的记录数作为总行数就会不对。

使用数据库来保存总记录行数

使用redis系统, 必定会有操作数据库和操作redis两步操作,这两步操作之间就会不一致。 如果我们在数据库中在使用一个单独的表来记录了总行数。 虽然也是需要两步操作,但是这两步操作却可以放在一个事务中执行, 由于MYSQL支持RR的隔离级别, 完美的避免了使用Redis的数据不一致的问题。

count(*) count(1) count(id) count(name) 的区别

  • count(1): 会自动选择合适的索引来统计, 每当统计到一条记录就累计值加一。 在扫描记录的时候,不会进行会表获取数据的。 只是计数
  • count(id) : 会使用主键索引来做统计
  • count(name):
    • name字段not null : 使用name字段上的索引(没有索引就全表扫描),扫描出来一行后就进行统计值加一
    • name 字段运行null: 使用name字段上的索引(没有索引就全表扫描),扫描出来一行后还要多一步判断是否为null, 如果不为null才会统计值加一, 为null则跳过。
  • count(id) : name 字段的 not null 情形是一样的。
  • count(*) : 这里就是计算所有行数, 至于选择什么索引都可以,只要能获取正确的值, 没必要把每一行的值取出来, 所以推荐使用这个来计算

使用Redis作为计数使用,并不是一个好的方案,因为他们是两个不同的存储系统,两个操作没有事务一致性。中间始终会出现数据不一致的情况的。

使用数据库中其他表来计数就没有这个问题, 但是我们是应该先插入数据,再来更新计数呢, 还是反过来呢?

答案: 应该是先更新数据,再更新计数。 因为计数这行数据会被很多的事务使用到, 对这个计数数据的行锁竞争会很激烈。 前面的优化讲过, 把竞争激烈的操作放在事务的后面可以提高事务的性能, 避免锁的过多竞争。 也可以使用多行数据来计数(和Java的LongAddr类一样), 表的总行数就是这几个数据的和。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值