『MySQL 实战 45 讲』14 - count(*) 慢的根本原因

文章探讨了MySQL中count(*)操作在MyISAM和InnoDB引擎下的性能差异,以及其慢速的根本原因,涉及到多版本并发控制的影响。MySQL对此进行了优化,但count(*)在高并发场景下仍可能较慢。解决方案包括使用缓存系统如Redis来保存计数,但这种方法可能存在逻辑不精确的问题,或者在数据库中创建单独的计数表以保证准确性。
摘要由CSDN通过智能技术生成

count(*) 慢的根本原因

count(*) 的实现方式

  1. MyISAM 引擎会把一个表的总行数存在了磁盘上
  2. InnoDB 引擎需要把数据一行行读出,累计计数

为什么 InnoDB 不跟 MyISAM 一样,也把数字存起来呢

  1. 由于多版本并发控制的原因(和快照读有关系),即同一时刻存在多个查询,InnoDB 表返回多少行不确定
  2. 例如三个用户对 A、B、C 并行开启事务会话,结构都不一样
  • 会话 A 先启动事务并查询一次表的总行数
  • 会话 B 启动事务,插入一行后记录后,查询表的总行数
  • 会话 C 先启动一个单独的语句,插入一行记录后,查询表的总行数
    在这里插入图片描述
  1. MYSQL 对 count(*) 操作,仍然做了优化
  • MySQL 优化器会找数据量少的索引树(包括主键、普通索引树比较)来遍历获取结果
  1. 不要使用下面命令来获取行数,其中的 TABLE_ROWS 字段是统计索引值,不准确
show table status

不同的 count 用法

  1. 首先明确,count() 是一个聚合函数,对于返回的结果集,一行行地判断,如果 count 函数的参数不是 NULL,累计值就加 1,否则不加
  2. 对于 count(主键 id) 、 count(1) 、 count(字段)、count(*) 的性能分析
  • count(主键 id)
    • 会遍历整张表,把每一行的 id 值都取出来,返回给 server 层。server 层拿到 id 后,判断是不可能为空的,就按行累加
  • count(1)
    • 遍历整张表,但不取值。server 层对于返回的每一行,放一个数字 “1” 进去,判断是不可能为空的,按行累加
  • count(字段)
    • 如果这个 “字段” 是定义为 not null 的话,一行行地从记录里面读出这个字段,判断不能为 null,按行累加
    • 如果这个 “字段” 定义允许为 null,需要判断累加
  • count(*)
    • 做了专门优化,不取值。count(*) 肯定不是 null,按行累加
  1. 效率排序
count(字段)<count(主键 id)<count(1)≈count(*)

解决方案

  1. 用缓存系统保存计数
  • 使用 Redis 服务保存这个表的总行数
    • 这个表每被插入一行 Redis 计数就加 1,每被除一行 Redis 计数就减 1
    • 然后把总行数定期地持久化存储起来
  • 如果 Redis 异常重启以后,则到数据库里面单独执行一次 count(*) 获取真实的行数,然后把值写回到 Redis 里
  • 注意: 将计数保存在缓存系统中的方式,还不只是丢失更新的问题。即使 Redis 正常工作,这个值还是逻辑上不精确的
  1. 在数据库保存计数
  • 计数直接放到数据库里单独的一张计数表 C 中
  • 如果有时序问题,就用事务来查表
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值