MySQL 实战 45 讲
林晓斌
网名丁奇,前腾讯云数据库负责人
224873 人已学习
新⼈⾸单¥68
登录后,你可以任选4讲全文学习
课程目录
已完结/共 49 讲
实践篇 (37讲)
特别放送 (1讲)
结课测试 (1讲)
MySQL 实战 45 讲
15
15
1.0x
00:00/00:00
登录|注册

14 | count(*)这么慢,我该怎么办?

计数不精确的问题
丢失更新的问题
事务序列中的操作顺序
性能差别
InnoDB引擎的优势
问题解决方法
count(*)
count(字段)
count(1)
count(主键id)
解决计数不精确的问题
崩溃恢复
Redis
InnoDB引擎
MyISAM引擎
思考题
问题解答
小结
不同的count用法
在数据库保存计数
用缓存系统保存计数
count(*)的实现方式
后续文章
总结

该思维导图由 AI 生成,仅供参考

在开发系统的时候,你可能经常需要计算一个表的行数,比如一个交易系统的所有变更记录总数。这时候你可能会想,一条 select count(*) from t 语句不就解决了吗?
但是,你会发现随着系统中记录数越来越多,这条语句执行得也会越来越慢。然后你可能就想了,MySQL 怎么这么笨啊,记个总数,每次要查的时候直接读出来,不就好了吗。
那么今天,我们就来聊聊 count(*) 语句到底是怎样实现的,以及 MySQL 为什么会这么实现。然后,我会再和你说说,如果应用中有这种频繁变更并需要统计表行数的需求,业务设计上可以怎么做。

count(*) 的实现方式

你首先要明确的是,在不同的 MySQL 引擎中,count(*) 有不同的实现方式。
MyISAM 引擎把一个表的总行数存在了磁盘上,因此执行 count(*) 的时候会直接返回这个数,效率很高;
而 InnoDB 引擎就麻烦了,它执行 count(*) 的时候,需要把数据一行一行地从引擎里面读出来,然后累积计数。
这里需要注意的是,我们在这篇文章里讨论的是没有过滤条件的 count(*),如果加了 where 条件的话,MyISAM 表也是不能返回得这么快的。
在前面的文章中,我们一起分析了为什么要使用 InnoDB,因为不论是在事务支持、并发能力还是在数据安全方面,InnoDB 都优于 MyISAM。我猜你的表也一定是用了 InnoDB 引擎。这就是当你的记录数越来越多的时候,计算一个表的总行数会越来越慢的原因。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

MySQL中的count(*)语句在不同引擎中有不同的实现方式。MyISAM引擎直接返回存储在磁盘上的总行数,效率高;而InnoDB引擎需要逐行读取数据并累积计数,导致执行速度变慢。针对频繁统计表行数的需求,建议自行计数或使用缓存系统保存计数,如Redis服务,但存在数据不一致和丢失更新的问题。另外,文章还详细解释了count()函数的不同用法的性能差别,以及在不同情况下使用事务来确保计数准确的方法。总的来说,文章深入浅出地介绍了MySQL技术特点,为读者提供了有益的技术参考。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《MySQL 实战 45 讲》
新⼈⾸单¥68
立即购买
登录 后留言

全部留言(250)

  • 最新
  • 精选
  • 发条橙子 。
    置顶
    老师 ,我这边有几个问题 : 1. 看到老师回复评论说 count(id) 也是走普通索引 ,那是不是也算是优化了 , 我以为 count(字段) 是走的聚集索引 。老师的意思是 count(字段) 是走二级索引,但是不一定是数据最少的索引树的意思是么 2. count(*) 的话, innodb 还会有取数判空这样的判断逻辑么 ,还是直接取行数 +1 了 , 还是按所取索引类型分情况。 允许为 null 的索引是不是行数比较少, 取的总数会不会有问题呢 3. 我这边试了一下 , 库里总共 30w 数据 。 第一次用 count(*) 是 120多ms , 第二次就是 60多 ms 。 第三次用了 count(1) ,也是60多ms 。 请问 count(*) 这两次的前后时间差是什么原因,也会走缓存 ? 4. 另一个问题是一个题外话 ,我看老师的例子事务级别应该都是 rr 。 我偶然看到我们公司事务隔离级别是 rc 。 我比较惊讶,就去问 DBA 为什么是 rc 而不是默认的 rr 。 她说一般都是用的 rc ,我想问现在公司一般都是 rc 么, 请问老师现在用的隔离级别是什么 ?? 在我的印象里 ,rr 保证事务的隔离性会更好一些吧 。 我google 了一下, rc 会不会在某些场景下出现一些问题,但是没有查出来相关结果。老师能不能讲解一下,rc 的话会在哪些场景下会踩坑么 。 (我之前码代码都是按照 rr 级别下的思维码的代码)

    作者回复: 1. 如果有索引用到这个字段的话,比较大可能会用到这个索引,比主键索引小 2. 索引字段就算是NULL,上面的id也不是的 3. 进了Buffer pool 的原因吧 4. 嗯,rc用得挺多的,但是原因可能只是因为“以前是这么用的”。 使用rc可能有问题,也可能没问题。但是我觉得DBA不知道为什么这么选,这个是问题。 rc本身的问题其实前面我们说过一些,比如不是一致性读。后面也会有文章说到。

    2018-12-15
    15
    23
  • 倪大人
    置顶
    看到有同学说会话A是幻读,其实图一的会话B才是幻读吧?

    作者回复: 这些都不叫幻读,幻读的意思是“用一个事务里面,后一个请求看到的比之前相同请求看到的,多了记录出来”。 改了不算 大家关注一下这个问题。 好问题

    2018-12-15
    16
    71
  • 果然如此
    置顶
    一、请问计数用这个MySQL+redis方案如何: 1.开启事务(程序中的事务) 2.MySQL插入数据 3.原子更新redis计数 4.如果redis更新成功提交事务,如果redis更新失败回滚事务。 二、.net和java程序代码的事务和MySQL事务是什么关系,有什么相关性?

    作者回复: 1. 好问题,不会还是没解决我们说的一致性问题。如果在3、4之间插入了 Session B的逻辑呢 2. 我估计就是启动事务(执行begin),结束时提交(执行commit)吧,没有了解过所有框架,不确定哈

    2018-12-14
    15
    27
  • 包包up
    置顶
    从并发系统性能的角度考虑,应该先插入操作记录,再更新计数表。 知识点在《行锁功过:怎么减少行锁对性能的影响?》 因为更新计数表涉及到行锁的竞争,先插入再更新能最大程度地减少了事务之间的锁等待,提升了并发度。

    作者回复: 好几个同学说对,你第一个标明出处👍🏿

    2018-12-14
    18
    682
  • 李二木
    一直以为带*查询效率是最差的,平时查询特意加了 count(ID) 查询。罪过啊。

    作者回复: 😄 来得及来得及

    2018-12-15
    16
    113
  • 菜鸡一只
    count(id)和count(这段)都是要把每一行的该字段值取出来,然后判断是否为空,那为什么count(id)的效率要高?

    作者回复: count(id)可能会选择最小的索引来遍历 而count(字段)的话,如果字段上没有索引,就只能选主键索引

    2019-02-01
    11
    90
  • 某、人
    老师我先问个本章之外的问题: 1.rr模式下,一张表上没有主键和唯一键,有二级索引c.如果是一张大表,删除一条数据delete t where c=1. 在主库上利用二级索引,在根据虚拟的主键列回表删除还挺快.但是在备库上回放特别慢,而且状态是system lock,是因为binlog event里没有包含虚拟主键列.导致在备库回放的时候,必须全表扫描,耗时特别久?还是其他原因 2.回放过程中,在备库delete一条语句是被阻塞的,insert又是可以的,说明只在记录上的X锁没有gap锁。 但是如果在主库session A begin,delete where c=1.在开启一个session B,在主库上操作也是delete阻塞,insert正常.不过等session A执行完成,不提交.insert都阻塞了,说明最后上了gap锁。有点没明白这儿的上锁逻辑是什么? 3.还有就是备库回放binlog,相对于主库的一条update语句流程来说,从库回放哪些流程是省略了的啊, server层的应该都省略了,应该主要是引擎层的回放,这里有点模糊从库是怎么回放的binlog event? 因为第一个问题从库回放的时候,从库上的二级索引貌似没起作用,直接就在聚簇索引上做的更新。 感谢老师

    作者回复: 1. 对,这个是个bug, 从库上会全表扫描。MariaDB 的版本有解决这个问题。生产上我们最好不允许没有主键的表 2. 按照你问的,gap锁没问题了。delete 被锁是因为行锁吧。从库重放就是因为走全表扫描按行锁下来触发的 3. 出现这个问题肯定是binlog设置了row格式。 这样binlog里面有所有值。如果你有主键的话,就是主键查,没有的话…就是全表了

    2018-12-14
    9
    26
  • 小动物很困
    我记得有一个并发插入的方法,就是说计数表对同一个表开很多行,然后计数增加对这些数据随机做加法,当做count操作的时候取sum,这样对行锁竞争会削弱.

    作者回复: 是的,咱们专栏07篇也有类似的介绍😆

    2019-01-22
    3
    21
  • 不忘初心
    对于 count(主键 id) ,server层拿到ID,判断ID是不可能为空的按行累加。这个地方,是不是又点问题,既然是主键ID,是一定不会为空的,这个server层还需要判断不为空吗

    作者回复: 嗯,代码就是这么写的 我也觉得可以优化一下… 不过现在就这样😓

    2018-12-26
    16
  • 陈天境
    碰到大部分情形都是带条件查询的count,,这个怎么解?

    作者回复: 索引条件过滤完后还多少行?如果行数少(几百行?)就没关系直接执行了

    2018-12-14
    6
    9
收起评论
显示
设置
留言
99+
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部