Count(*)语句是怎么实现的

406 阅读2分钟

这是我参与8月更文挑战的第8天,活动详情查看:8月更文挑战

count(*)的实现方式

  1. MyISAM - 表的总行数存在磁盘上 - 直接返回这个数 - 效率高【没有过滤条件的count(*)】
  2. InnoDB - 数据一行行从引擎中读出来 - 累积计数

为什么InnoDB不把数字也存起来?

由于多版本并发控制(MVCC) - InnoDB表“应该返回多少行”也不确定
InnoDB的事务涉及 - 默认隔离级别是可重复读 - MVCC实现 - 每行记录都要判断自己是否对这个会话可见 -- 数据一行行读出依次判断,可见的行才能用于计算“基于这个查询”的表的总行数。

InnoDB - 索引组织表 - 主键索引树的叶子节点是数据,普通索引树的叶子节点是主键值 - 普通索引树比主键索引树小很多 - count(*) 遍历哪个索引树的到的结果逻辑上都一样 - MySQL优化器会找到最小的那棵树来遍历 -在保证逻辑正确的前提下,尽量减少扫描的数据量 - 数据库系统涉及的通用法则之一

show table status命令输出结果中的TABLE_ROWS用于显示这个表当前有多少行
索引统计的值是通过采样来估算的 - TABLE_ROWS就是这个采样估算得来 - 因此不准

小结

  1. MyISAM 表count(*)很快 - 不支持事务;
  2. show table status 命令返回很快 - 不准确;
  3. InnoDB表之间count(*)会遍历全表 - 结果准确但会导致性能问题。

自己计数 - 找地方存操作记录表得行数

  1. 用缓存系统保存计数
    更新很频繁的库 - 缓存系统来支持 - Redis服务来保存这个表的总行数 - 表每被插入一行Redis计数就加1,每被删除一行Redis计数就减1 - 读和更新操作都很快 -缓存系统可能会丢失更新
    Redis 的数据不会永久地留在内存里 - 找地方将值定期持久化存储 - 仍然可能丢失更新【刚在数据表中插入了一行,Redis中保存的值也加1,Redis异常重启了】 - 数据库里单独执行一次count(*)获取真实行数 - 再把值写回到Redis里

计数保存在缓存系统中的方式,不只是丢失更新的问题。即使Redis正常工作,这个值在逻辑上不精确

  1. 在数据库保存计数
    将这个计数直接放到数据库里单独的一张计数表C中
    解决了崩溃问题【InnoDB 支持崩溃恢复不丢数据】
    解决了值逻辑上不一致的问题

不同的count用法

  1. 返回满足条件的结果集的总行数 - count(*)、count(主键 id)、count(1)

  2. 返回满足条件的数据行里,该字段不为NULL的总个数 - count(字段)