我说用count(*)统计行数,面试官让我回去等消息...

MySQL COUNT(*) 性能优化全解析:从原理到实战避坑指南 在数据库操作中,数据统计是基础且高频的需求,其核心是快速准确地获取数据集合的规模。从原理层面看,数据库通过索引扫描和事务可见性判断来实现行数统计,其中存储引擎的设计(如InnoDB的MVCC机制)直接影响统计效率。这项技术的价值在于支撑业务分页、数据监控和报表汇总等关键场景,确保系统可观测性。然而,在大数据量表或高并发环境下,简单的COUNT操作可能引发严重的性能瓶颈。本文聚焦MySQL COUNT(*)的深度优化,探讨如何通过索引选择、计数表维护及架构升级等手段,在保证数据一致性的同时大幅提升查询性能,并 阅读详情

点击上方“芋道源码”,选择“设为星标”

管她前浪,还是后浪?

能浪的浪,才是好浪!

每天 10:33 更新文章,每天掉亿点点头发...

源码精品专栏

 

来源:苏三说技术

d93d588d1b795b6b4d26a66377836d6e.jpeg


前言

最近我在公司优化过几个慢查询接口的性能,总结了一些心得体会拿出来跟大家一起分享一下,希望对你会有所帮助。

我们使用的数据库是Mysql8,使用的存储引擎是Innodb。这次优化除了优化索引之外,更多的是在优化count(*)。

通常情况下,分页接口一般会查询两次数据库,第一次是获取具体数据,第二次是获取总的记录行数,然后把结果整合之后,再返回。

查询具体数据的sql,比如是这样的:`

select id,name from user limit 1,20;

它没有性能问题。

但另外一条使用count(*)查询总记录行数的sql,例如:

select count(*) from user;

却存在性能差的问题。

为什么会出现这种情况呢?

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/ruoyi-vue-pro

  • 视频教程:https://doc.iocoder.cn/video/

1 count(*)为什么性能差?

在Mysql中,count(*)的作用是统计表中记录的总行数。

而count(*)的性能跟存储引擎有直接关系,并非所有的存储引擎,count(*)的性能都很差。

在Mysql中使用最多的存储引擎是:innodb和myisam。

在myisam中会把总行数保存到磁盘上,使用count(*)时,只需要返回那个数据即可,无需额外的计算,所以执行效率很高。

而innodb则不同,由于它支持事务,有MVCC(即多版本并发控制)的存在,在同一个时间点的不同事务中,同一条查询sql,返回的记录行数可能是不确定的。

在innodb使用count(*)时,需要从存储引擎中一行行的读出数据,然后累加起来,所以执行效率很低。

如果表中数据量小还好,一旦表中数据量很大,innodb存储引擎使用count(*)统计数据时,性能就会很差。

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/yudao-cloud

  • 视频教程:https://doc.iocoder.cn/video/

2 如何优化count(*)性能?

从上面得知,既然count(*)存在性能问题,那么我们该如何优化呢?

我们可以从以下几个方面着手。

2.1 增加redis缓存

对于简单的count(*),比如:统计浏览总次数或者浏览总人数,我们可以直接将接口使用redis缓存起来,没必要实时统计。

当用户打开指定页面时,在缓存中每次都设置成count = count+1即可。

用户第一次访问页面时,redis中的count值设置成1。用户以后每访问一次页面,都让count加1,最后重新设置到redis中。a66f6e231039b36d7a1d4d68ed970c4c.png这样在需要展示数量的地方,从redis中查出count值返回即可。

该场景无需从数据埋点表中使用count(*)实时统计数据,性能将会得到极大的提升。

不过在高并发的情况下,可能会存在缓存和数据库的数据不一致的问题。

但对于统计浏览总次数或者浏览总人数这种业务场景,对数据的准确性要求并不高,容忍数据不一致的情况存在。

2.2 加二级缓存

对于有些业务场景,新增数据很少,大部分是统计数量操作,而且查询条件很多。这时候使用传统的count(*)实时统计数据,性能肯定不会好。

假如在页面中可以通过id、name、状态、时间、来源等,一个或多个条件,统计品牌数量。

这种情况下用户的组合条件比较多,增加联合索引也没用,用户可以选择其中一个或者多个查询条件,有时候联合索引也会失效,只能尽量满足用户使用频率最高的条件增加索引。

也就是有些组合条件可以走索引,有些组合条件没法走索引,这些没法走索引的场景,该如何优化呢?

答:使用二级缓存。

二级缓存其实就是内存缓存。

我们可以使用caffine或者guava实现二级缓存的功能。

目前SpringBoot已经集成了caffine,使用起来非常方便。

只需在需要增加二级缓存的查询方法中,使用@Cacheable注解即可。

@Cacheable(value = "brand", , keyGenerator = "cacheKeyGenerator")
   public BrandModel getBrand(Condition condition) {
       return getBrandByCondition(condition);
   }

然后自定义cacheKeyGenerator,用于指定缓存的key。

public class CacheKeyGenerator implements KeyGenerator {
    @Override
    public Object generate(Object target, Method method, Object... params) {
        return target.getClass().getSimpleName() + UNDERLINE
                + method.getName() + ","
                + StringUtils.arrayToDelimitedString(params, ",");
    }
}

这个key是由各个条件组合而成。

这样通过某个条件组合查询出品牌的数据之后,会把结果缓存到内存中,设置过期时间为5分钟。

后面用户在5分钟内,使用相同的条件,重新查询数据时,可以直接从二级缓存中查出数据,直接返回了。

这样能够极大的提示count(*)的查询效率。

但是如果使用二级缓存,可能存在不同的服务器上,数据不一样的情况。我们需要根据实际业务场景来选择,没法适用于所有业务场景。

2.3 多线程执行

不知道你有没有做过这样的需求:统计有效订单有多少,无效订单有多少。

这种情况一般需要写两条sql,统计有效订单的sql如下:

select count(*) from order where status=1;

统计无效订单的sql如下:

select count(*) from order where status=0;

但如果在一个接口中,同步执行这两条sql效率会非常低。

这时候,可以改成成一条sql:

select count(*),status from order
group by status;

使用group by关键字分组统计相同status的数量,只会产生两条记录,一条记录是有效订单数量,另外一条记录是无效订单数量。

但有个问题:status字段只有1和0两个值,重复度很高,区分度非常低,不能走索引,会全表扫描,效率也不高。

还有其他的解决方案不?

答:使用多线程处理。

我们可以使用CompleteFuture使用两个线程异步调用统计有效订单的sql和统计无效订单的sql,最后汇总数据,这样能够提升查询接口的性能。

2.4 减少join的表

大部分的情况下,使用count(*)是为了实时统计总数量的。

但如果表本身的数据量不多,但join的表太多,也可能会影响count(*)的效率。

比如在查询商品信息时,需要根据商品名称、单位、品牌、分类等信息查询数据。

这时候写一条sql可以查出想要的数据,比如下面这样的:

select count(*)
from product p
inner join unit u on p.unit_id = u.id
inner join brand b on p.brand_id = b.id
inner join category c on p.category_id = c.id
where p.name='测试商品' and u.id=123 and b.id=124 and c.id=125;

使用product表去join了unit、brand和category这三张表。

其实这些查询条件,在product表中都能查询出数据,没必要join额外的表。

我们可以把sql改成这样:

select count(*)
from product
where name='测试商品' and unit_id=123 and brand_id=124 and category_id=125;

在count(*)时只查product单表即可,去掉多余的表join,让查询效率可以提升不少。

2.5 改成ClickHouse

有些时候,join的表实在太多,没法去掉多余的join,该怎么办呢?

比如上面的例子中,查询商品信息时,需要根据商品名称、单位名称、品牌名称、分类名称等信息查询数据。

这时候根据product单表是没法查询出数据的,必须要去join:unit、brand和category这三张表,这时候该如何优化呢?

答:可以将数据保存到ClickHouse。

ClickHouse是基于列存储的数据库,不支持事务,查询性能非常高,号称查询十几亿的数据,能够秒级返回。

为了避免对业务代码的嵌入性,可以使用Canal监听Mysql的binlog日志。当product表有数据新增时,需要同时查询出单位、品牌和分类的数据,生成一个新的结果集,保存到ClickHouse当中。

查询数据时,从ClickHouse当中查询,这样使用count(*)的查询效率能够提升N倍。

需要特别提醒一下:使用ClickHouse时,新增数据不要太频繁,尽量批量插入数据。

其实如果查询条件非常多,使用ClickHouse也不是特别合适,这时候可以改成ElasticSearch,不过它跟Mysql一样,存在深分页问题。

3 count的各种用法性能对比

既然说到count(*),就不能不说一下count家族的其他成员,比如:count(1)、count(id)、count(普通索引列)、count(未加索引列)。

那么它们有什么区别呢?

  • count(*) :它会获取所有行的数据,不做任何处理,行数加1。

  • count(1):它会获取所有行的数据,每行固定值1,也是行数加1。

  • count(id):id代表主键,它需要从所有行的数据中解析出id字段,其中id肯定都不为NULL,行数加1。

  • count(普通索引列):它需要从所有行的数据中解析出普通索引列,然后判断是否为NULL,如果不是NULL,则行数+1。

  • count(未加索引列):它会全表扫描获取所有数据,解析中未加索引列,然后判断是否为NULL,如果不是NULL,则行数+1。

由此,最后count的性能从高到低是:

count(*) ≈ count(1) > count(id) > count(普通索引列) > count(未加索引列)

所以,其实count(*)是最快的。

意不意外,惊不惊喜?

千万别跟select * 搞混了。



欢迎加入我的知识星球,一起探讨架构,交流源码。加入方式,长按下方二维码噢:

8adfc3c8b62746700a81f86feeaa3af5.png

已在知识星球更新源码解析如下:

97473c14f157c7593e64ed890880b0b1.jpeg

f0da1e065e3bc0080add5d2b3b3c0414.jpeg

325a848fabe89dccdd6e2cfdf50a6638.jpeg

1859b06ef833f54d4b9fe80c6ec9b62a.jpeg

最近更新《芋道 SpringBoot 2.X 入门》系列,已经 101 余篇,覆盖了 MyBatis、Redis、MongoDB、ES、分库分表、读写分离、SpringMVC、Webflux、权限、WebSocket、Dubbo、RabbitMQ、RocketMQ、Kafka、性能测试等等内容。

提供近 3W 行代码的 SpringBoot 示例,以及超 4W 行代码的电商微服务项目。

获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。

文章有帮助的话,在看,转发吧。
谢谢支持哟 (*^__^*)
B站直播业务部实习面经复盘:那些我踩过的坑和面试官没明说的加分项 本文深度解析B站直播业务部实习面试中的技术细节与策略,涵盖Spring IOC实现原理、Redis缓存问题解决方案及业务场景应用。通过真实案例和代码示例,帮助求职者提升技术深度与业务敏感度,掌握面试官未明说的加分项,如项目经验转化和开放型问题应答技巧。 阅读详情

相关推荐

【MySQL】MySQL count(*) count(1) 实现方式以及各种 count 对比

在这个文章之前,我一直用count(1) 查询所有数据,以前我们都是说 count(*) 是最慢的。但是这个博客恰恰相反。对于来说,InnoDB 引擎会遍历整张表,把每一行的 id 值都取出来,返回给 server 层。server 层拿到 id 后,判断是不可能为空的,就按行累加。对于 count(1) 来说,InnoDB 引擎遍历整张表,但不取值。server 层对于返回的每一行,放一个数字“1”进去,判断是不可能为空的,按行累加。

九师兄 837

我说用count(*)统计行数,面试官让我回去等消息

最近我在公司优化过几个慢查询接口的性能,总结了一些心得体会拿出来跟大家一起分享一下,希望对你会有所帮助。我们使用的数据库是Mysql8,使用的存储引擎是Innodb。这次优化除了优化索引之外,更多的是在优化count(*)。通常情况下,分页接口一般会查询两次数据库,第一次是获取具体数据,第二次是获取总的记录行数,然后把结果整合之后,再返回。查询具体数据的sql,比如是这样的:select id,name from user limit&

jjc4261的博客 267

第75篇 Python在机器人测试中的应用:面试官问“你怎么保证代码质量”,别只说“手动测过”

摘要:本文探讨了ROS2中Python在自动化测试的应用场景。从单元测试(pytest验证核心算法)、集成测试(launch_testing验证节点交互)、仿真测试(PyBullet虚拟环境验证)到性能回归测试(基准测试集成CI),详细介绍了Python在不同测试阶段的具体实现方法。文章结合面试案例,强调自动化测试对工程素养的重要性,建议开发者从单元测试入手,逐步构建完整的测试体系,并分享了感知模块的Python实战经验(多进程/Cython优化)。

郑伟强dev的专栏 363

我说用count(*)统计行数,面试官让我回去等消息?

最近我在公司优化过几个慢查询接口的性能,总结了一些心得体会拿出来跟大家一起分享一下,希望对你会有所帮助。我们使用的数据库是Mysql8,使用的存储引擎是Innodb。这次优化除了优化索引之外,更多的是在优化count(*)。通常情况下,分页接口一般会查询两次数据库,第一次是获取具体数据,第二次是获取总的记录行数,然后把结果整合之后,再返回。查询具体数据的sql,比如是这样的:select id,name from user limit&

Java_ttcd的博客 480

DAP-Link DIY复刻指南

DAP-Link是ARM开源的一个项目,主要用于代码下载以及调试,支持SWD和JTAG调试接口,对于开发嵌入式的同学相信都或多或少有所耳闻。除了DAP-Link之外还有很多同类型的调试器,如ST-LinkJ-Link,但是这些都是不开源的,无法复刻或进行二次开发。但DAP-Link不同,完全开源,不用担心侵权问题,且随着开源社区大家的共同努力,DAP-Link经过了不断的迭代,先如今已取代ARM之前推出的CMSIS-DAP开源项目。

爱出名的狗腿子 1万+

ABAP BOM展开避坑指南:CS_BOM_EXPL_MAT_V2函数参数配置详解

本文深入解析了SAP ABAP开发中用于物料清单展开的核心函数CS_BOM_EXPL_MAT_V2。针对其复杂的参数配置,如CAPID、STLAN、DATUV和MEHRS,提供了详细的避坑指南和最佳实践,帮助开发者避免数据错误与性能陷阱,实现高效、准确的BOM多层展开。

pea55的博客 856

我用count(*)统计行数,面试官让我回去等消息

最近我在公司优化过几个慢查询接口的性能,总结了一些心得体会拿出来跟大家一起分享一下,希望对你会有所帮助。我们使用的数据库是Mysql8,使用的存储引擎是Innodb。这次优化除了优化索引之外,更多的是在优化count(*)。通常情况下,分页接口一般会查询两次数据库,第一次是获取具体数据,第二次是获取总的记录行数,然后把结果整合之后,再返回。查询具体数据的sql,比如是这样的:select id,name from user limit&

资料免费分享,点击名片 323

初识Renode

1. 简介 Renode是Antmicro公司发布的一个开发框架,使得开发人员通过模拟物理硬件系统(包括CPU、外围设备、传感器、环境以及节点之间的有线或无线介质),加速物联网和嵌入式系统的开发。它可在PC上通过软件对嵌入式设备进行模拟,可模拟Cortex-M、Cortex-A、RISC-V和其他CPU,并可模拟外设以及各种通信总线和接口。其指令模拟器使用C语言开发,外设模拟器使用C#语言开发,测试及输出层面的内容则用python编写。 Renode官网:Renodehttps://renode.io/

牧羊女 5296

不就是Select Count语句吗,竟然能被面试官虐的体无完肤!

点击上方“Java后端技术”,选择“置顶或者星标”你关注的就是我关心的!作者:Hollis |来源:Hollis(ID:hollischuang)上一篇:数据库查询相信很多人都不陌生,所有经常有人调侃程序员就是CRUD专员,这所谓的CRUD指的就是数据库的增删改查。在数据库的增删改查操作中,使用最频繁的就是查询操作。而在所有查询操作中,统计数量操作更是经常被用到。关于数据库中行数统计,无论是My...

Java后端技术 571

Python读取三菱PLC数据

3、支持自定义的数据读写操作。直接调用Read方法,可以读取到原生的byte数组,然后进行组合数据。1、每次的数据交互您都可以判断是否成功,如果您不判断,如果网络断开或是异常,会影响程序的执行。Qna3E协议的ascii类 MelsecMcAsciiNet。A兼容1E协议的二进制类 MelsecA1ENet。Qna3E协议的二进制类 MelsecMcNet。2、读写操作支持单个数和数组。

weixin_46744807的博客 2000

【深度强化学习】(2) Double DQN 模型解析,附Pytorch完整代码

Double DQN 算法是 DQN 算法的改进版本,解决了 DQN 算法过估计行为价值的问题。Double DQN 算法不直接通过最大化的方式选取目标网络计算的所有可能 Q 值,而是首先通过估计网络选取最大 Q 值对应的动作。在 Double DQN 框架中存在两个神经网络模型,分别是训练网络与目标网络。这两个神经网络模型的结构完全相同,但是权重参数不同;每训练一段之间后,训练网络的权重参数才会复制给目标网络。

博观而约取,厚积而薄发 1万+

数据库COUNT(*)性能优化与高并发计数方案全解析

在数据库查询优化中,统计行数是一个基础但至关重要的操作。从原理上讲,COUNT(*)函数在InnoDB引擎中会优先选择非空二级索引进行扫描,避免读取庞大的聚簇索引数据块,这是其性能优化的核心机制。理解这一原理对于设计高性能数据统计功能具有重要技术价值,尤其是在高并发和大数据量场景下。在实际应用场景中,直接使用COUNT(*)可能面临性能瓶颈,因此衍生出多种优化方案,例如通过维护独立的计数表实现实时精准统计,或者利用Redis缓存结合消息队列实现最终一致性计数。这些方案能够有效应对千万级甚至亿级数据表的行数统

weixin_42640676的博客 549

基于gem5模拟trace的cache模拟器的实现

在做计算机体系结构的时候,模拟器是一个重要且又有效的工具。其中gem5集成了gem和m5的优点,使用起来比较简单和方便。其中包括了se模式和fs模式,se模式是在gem5运行我们已经编译好的程序,可以获取cache,cpu状态等数据,fs模式下可以模拟操作系统,我们可以将我们自己修改的linux内核加载到哥们上运行。下面介绍了使用gem5的se模式,运行编译好的ARM可执行文件。获取访存的trac

gaoxiang的专栏 8893

面试官追问:RabbitMQ消息幂等性设计的实战策略与陷阱规避

本文深入探讨了RabbitMQ消息幂等性设计的实战策略与陷阱规避,分析了消息重复消费的三大根源,并提出了四层防御体系,包括业务层去重、数据库约束、分布式锁方案和消息指纹方案。文章还分享了高并发下的优化技巧和面试应答策略,帮助开发者在分布式系统中确保消息处理的可靠性。

374

海思Hi3516CV610开发实战:在Ubuntu 22.04上从零构建嵌入式Linux开发环境

本文详细介绍了在Ubuntu 22.04上为海思Hi3516CV610开发板搭建嵌入式Linux开发环境的完整流程。从虚拟机配置、系统安装到基础环境设置,提供了实用技巧和常见问题解决方案,帮助开发者高效构建适用于智能视觉处理的开发环境。

weixin_29197699的博客 510

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

Java实习模拟面试 | 美团27届后端开发二面:深入数据库配置、微服务架构与消息中间件原理

大厂极其重视“确定性”和“生产细节”。❌ 不要说“大概”、“可能”、“我记得”;✅ 要说“我们可以查监控”、“配置在 XX 文件”、“日志格式为 XXX”。此外,原理必须能讲到源码/协议层,比如 Nacos 的 Pull+Push、Kafka 的分区策略、Feign 的动态代理链路。📌建议如果你没真正上线过系统,至少要模拟一套完整的生产配置方案服务器规格JVM 参数中间件拓扑图监控告警策略这样才能在面试中应对连环追问而不慌。

吾为热爱计科,致力于知识分享的践行者,此博客是我在这条道路上留下的足迹。 1074

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

Java 实习模拟面试之信也科技:IO、多线程、集合、MySQL、Redis、HTTP、Linux 常见面试题解析

客户端发送SYN=1,随机生成初始序号 seq=x;服务端回复SYN=1和ACK=1(确认序号=x+1),随机生成初始序号 seq=y;客户端发送ACK=1(确认序号=y+1)。三次握手的目的是为了防止已失效的连接请求突然传到服务器,造成资源浪费。本次模拟面试涵盖了 Java 基础、MySQL、Redis、MQ、HTTP、Linux 等多个技术点,展示了作为 Java 实习生应具备的核心知识体系。建议在准备面试时,不仅要熟悉这些知识点,还要结合实际项目经验进行深入理解,提升综合解决问题的能力。

吾为热爱计科,致力于知识分享的践行者,此博客是我在这条道路上留下的足迹。 1195

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

字节跳动2018校招大数据面试复盘:Hadoop、Spark与Kafka考点解析

大数据技术栈是构建现代数据链路的核心,Hadoop提供分布式存储与计算基础,Spark以内存计算提升处理效率,Kafka则保障数据流转的可靠性与实时性。理解HDFS读写、MapReduce Shuffle、Spark宽窄依赖、Kafka消息可靠性等底层原理,是解决数据倾斜、系统设计、海量数据TopK等实际问题的关键。这些技术能力在大规模日志处理、实时指标统计、离线数仓建设等场景中发挥着重要作用。对于准备大数据方向校招的开发者,掌握这些组件在真实业务中的设计取舍与调优策略,远比死记硬背面经更有价值。本文结合字

weixin_33829657的博客 549

2025最全Java面试题及答案汇总

本文整理了200期Java面试题合集,涵盖基础语法、多线程、JVM、MySQL、Spring、Redis等核心技术点,包含"为什么MySQL不推荐使用join"、"SpringBoot启动流程"等高频率面试题。文章按专题分类整理,旨在帮助开发者系统复习面试高频考点,强调面试准备的重要性,建议开发者通过刷题补充理论知识短板。同时提供完整面试题打包下载资源,助力求职者高效备战技术面试。

A345545108的博客 1137
上一篇: 原来这是缓存啊,被虐了!
下一篇: Nginx 轻松搞定跨域问题
公众号-芋道源码
博客等级 码龄10年 1636粉丝 377原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值