百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

面试官:count(*) 怎么优化?(countdistinct优化)

mhr18 2025-04-06 18:22 17 浏览 0 评论

01 前言

哈喽,我是狗哥。小伙伴都知道我最近换工作了,薪资、工作内容什么的都是我比较满意的。五月底也面试了有 6、7 家公司,应该拿了有 5 个 offer。这段时间也被问了很多面试题,我打算写一个专题分享出来,希望对你们有所帮助~

这期面试官提的问题是:

count (1) 和 count (*) 有啥区别?你更推荐用哪个?数据量很大的情况下怎么优化?

国际惯例先上思维导图:

1.1 往期精彩

MySQL 查询语句是怎么执行的?

MySQL 索引

MySQL 日志

MySQL 事务与 MVCC

MySQL 的锁机制

MySQL 字符串怎么设计索引?

面试官:数据库自增 ID 用完了会咋样?

面试官:order by 怎么优化?

02 四种 count 的区别

count 是一个聚合函数,对于返回的结果集,一行行地判断,如果 count 函数的参数不是 NULL,累计值就加 1,否则不加。最后返回累计值。

既然都说到这里了,干脆就把 4 种 count 的区别都对比下:

  • count (字段):遍历整张表,需要取值,判断 字段!= null,按行累加;
  • count (主键) :遍历整张表,需要取 ID,判断 id !=null,按行累加;
  • count (1) :遍历整张表,不取值,返回的每一行放一个数字 1,按行累加;
  • count (*):不会把全部字段取出,专门做了优化,不取值。count ( * ) 肯定不是 null,按行累加。

count (主键) 可能会选择最小的索引来遍历,而 count (字段) 的话,如果字段上没有索引,就只能选主键索引,所以性能上 count (字段) < count (主键)

因为 count (*) 和 count (1) 不取字段值,减少往 server 层的数据返回,所以比其他 count (字段) 要返回值的性能较好;

所以结论是:** 按照效率排序的话,count (字段)<count (主键 id)<count (1)≈count (),建议尽量使用 count ()。

2.1 MySQL 对 count (*) 做的优化

InnoDB 是索引组织表,主键索引树的叶子节点是数据,而普通索引树的叶子节点是主键值。因此,普通索引树比主键索引树小很多

对于 count (*) 来说,遍历哪个索引树得到的结果逻辑上都是一样的。MySQL 优化器会找到最小的那棵树来遍历。在保证逻辑正确的前提下,尽量减少扫描的数据量,是数据库系统设计的通用法则之一

03 count (*) 的实现方式

count (*) 在不同引擎中的实现方式是不一样的:

  • MyISAM:不支持事务,把一个表的总行数存在了磁盘上,因此执行 count (*) 的时候会直接返回这个数,效率很高;
  • InnoDB:支持事务,它执行 count (*) 的时候,需要把数据一行一行地从引擎里面读出来,然后累积计数。

当然这里讨论的是没有 where 条件下的 count,如果有 where 条件,那么即使是 MyISAM 也必须累积计数的。

至于有 where 条件怎么执行,建议看看海神的这篇文章:

SELECT COUNT (*) 会造成全表扫描吗?

当你的记录数越来越多的时候,计算一个表的总行数会越来越慢。你可能会问:

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

其实是因为 InnDB 支持事务的 MVCC 的原因,当前时刻的 SQL 应该返回的记录数是多少,它也需要扫描才知道。不知道 MVCC 的,可以看看之前的旧文:

MySQL 事务与 MVCC

看完还不懂?举个例子:假设表 t 中现在有 10000 条记录,有三个用户并行的会话。

  • 会话 A 先启动事务并查询一次表的总行数;
  • 会话 B 启动事务,插入一行后记录后,查询表的总行数;
  • 会话 C 先启动一个单独的语句,插入一行记录后,查询表的总行数。

它的执行流程以及结果是这样的:

A、B、C

你也发现了,因为 MVCC 机制,事务之间是存在可见性的。所以,并发环境下每个会话得到的数据是不一样的。

分析:

  • 会话 A 在 C 之前启动,C 可见 A 且会话 C 自己插入一行,再 count (*),对它自己来说肯定是可见的、所以结果 +1。
  • 会话 A、C 在 B 之前启动,B 可以看见 A、C,自己插入一条数据 +1、C 插入一条数据 +1、所以 B 结果 + 2

04 TABLE_ROWS 能代替 count (*) 吗?

如果你看过官方文档的话,你会知道 show table status 命令,它的结果有个 ROWS 字段就是估算该表的数据量,如下所示:

真实数据:

图一是估算数据、图二是真实数据。实际上你会发现两种数据不一致,因为 show table status 命令对数量的统计是估算的,并不准确。

到这里我们小结一下:

  • MyISAM 表虽然 count (*) 很快,但是不支持事务;
  • show table status 命令虽然返回很快,但是不准确;
  • InnoDB 表直接 count (*) 会遍历全表,虽然结果准确,但会导致性能问题。

那么问题来了:假设我现在有个订单页面,更新很频繁,并且需求是要显示实时的操作记录总数、并且展现最新的 100 条记录信息。应该用哪种方式呀?

很明显只能自己计数呀,那么如何设计呢?

05 基于 count (*) 的计数方案

基本思路就是:你需要自己找一个地方,把操作记录表的行数存起来

5.1 结果放在 Redis

更新频繁,我第一时间肯定是想到 Redis 这神器呀。表插入一行 Redis 计数加一,删除一行计数减一。Redis 性能贼好,听起来这方案似乎完美。

仔细一想,还是有 ** 丢失更新的问题:MySQL 插入一行,Redis 宕机咋办?** 你可能会说,恢复之后再执行一次 count (*),再次缓存不就得了?

好,丢失更新的问题确实解决了,但是 MySQL 和 Redis 的数据怎么保证一致性呢?

假设我现在要去最新的 100 条数据,并在前端展现。时序图如下:

很明显,会话 A 插入数据,但是还没来得及更新 Redis;会话 B 查询 Redis 计数,并向 MySQL 查询最新的 100 条记录。

此时数据就不精确:查到的 100 行结果里面有最新插入记录,而 Redis 的计数里还没加 1,总数不精确

有人可能说,你 SessionA 换个顺序不就好了。先更新 Redis 计数、再插入 MySQL 表记录。像下面这样

其实在 T3 时刻还是会出现不一致的情况:查到的 100 行结果里面没有最新插入记录,而 Redis 的计数里加了 1,最新记录不精确

所以说,用 Redis 保存计数有丢失数据和计数不精确的问题。

5.2 结果放在 MySQL

上面出现数据丢失或计算不精确的原因在于:MySQL 和 Redis 的事务不是同一体系的,我们并不能保证两者事务的原子性,而把 Redis 也换成 MySQL 这就迎刃而解了。

那我们换个思路,不能新建一张 MySQL 表 C 专门用来存放订单表的总数吗?

看到这里,你可能会说这不跟开头冲突了么?由于 InnoDB 要支持事务,从而导致 InnoDB 表不能把 count (*) 直接存起来,然后查询的时候直接返回计算好的。你现在说又能存,这不扯了么?

其实我们可以利用事务原子性和隔离特性解决这一问题:表 C 计数器的修改和订单数据的写表在一个事务中。读取计数器和查询最近订单数据也在一个事务中。看到这里,有没有清晰一点?

我来画个时序图:

会话 A 进行写操作,T3 时刻,A 的更新事务还没有提交;所以计数值加 1 这个操作对会话 B 还不可见。也就是说会话 B 看到的结果在逻辑上就是一致的

看到这里是不是有点,成也事务败也事务的感觉?

06 总结

首先,在 4 中 count 的对比中,我们应该选 count (*),因为 MySQL 对它作做了优化;

第二,count (*) 在两种搜索引擎中的实现是不一样的,MyIsam 直接把总数存在硬盘、而 InnDB 则是老式计数;

第三,分析了 Redis 存储计数会出现的问题,把计数值也放在 MySQL 中,利用事务的原子性和隔离性,就可以解决一致性的问题。

最后,数据量不大,我们尽量用 count (*) 实现计数;数据量很大的情况考虑新建 MySQL 表存储计数,用事务的原子性和隔离性解决。

参考

  • time.geekbang.org/column/article/72775
  • blog.csdn.net/bjweimengshu/article/details/79607522
  • cnblogs.com/shoshana-kong/p/10516404.html

原文链接:
https://mp.weixin.qq.com/s/bYOKQfTwriZH-zFiINoyEQ

相关推荐

【推荐】一个开源免费、AI 驱动的智能数据管理系统,支持多数据库

如果您对源码&技术感兴趣,请点赞+收藏+转发+关注,大家的支持是我分享最大的动力!!!.前言在当今数据驱动的时代,高效、智能地管理数据已成为企业和个人不可或缺的能力。为了满足这一需求,我们推出了这款开...

Pure Storage推出统一数据管理云平台及新闪存阵列

PureStorage公司今日推出企业数据云(EnterpriseDataCloud),称其为组织在混合环境中存储、管理和使用数据方式的全面架构升级。该公司表示,EDC使组织能够在本地、云端和混...

对Java学习的10条建议(对java课程的建议)

不少Java的初学者一开始都是信心满满准备迎接挑战,但是经过一段时间的学习之后,多少都会碰到各种挫败,以下北风网就总结一些对于初学者非常有用的建议,希望能够给他们解决现实中的问题。Java编程的准备:...

SQLShift 重大更新:Oracle→PostgreSQL 存储过程转换功能上线!

官网:https://sqlshift.cn/6月,SQLShift迎来重大版本更新!作为国内首个支持Oracle->OceanBase存储过程智能转换的工具,SQLShift在过去一...

JDK21有没有什么稳定、简单又强势的特性?

佳未阿里云开发者2025年03月05日08:30浙江阿里妹导读这篇文章主要介绍了Java虚拟线程的发展及其在AJDK中的实现和优化。阅前声明:本文介绍的内容基于AJDK21.0.5[1]以及以上...

「松勤软件测试」网站总出现404 bug?总结8个原因,不信解决不了

在进行网站测试的时候,有没有碰到过网站崩溃,打不开,出现404错误等各种现象,如果你碰到了,那么恭喜你,你的网站出问题了,是什么原因导致网站出问题呢,根据松勤软件测试的总结如下:01数据库中的表空间不...

Java面试题及答案最全总结(2025版)

大家好,我是Java面试陪考员最近很多小伙伴在忙着找工作,给大家整理了一份非常全面的Java面试题及答案。涉及的内容非常全面,包含:Spring、MySQL、JVM、Redis、Linux、Sprin...

数据库日常运维工作内容(数据库日常运维 工作内容)

#数据库日常运维工作包括哪些内容?#数据库日常运维工作是一个涵盖多个层面的综合性任务,以下是详细的分类和内容说明:一、数据库运维核心工作监控与告警性能监控:实时监控CPU、内存、I/O、连接数、锁等待...

分布式之系统底层原理(上)(底层分布式技术)

作者:allanpan,腾讯IEG高级后台工程师导言分布式事务是分布式系统必不可少的组成部分,基本上只要实现一个分布式系统就逃不开对分布式事务的支持。本文从分布式事务这个概念切入,尝试对分布式事务...

oracle 死锁了怎么办?kill 进程 直接上干货

1、查看死锁是否存在selectusername,lockwait,status,machine,programfromv$sessionwheresidin(selectsession...

SpringBoot 各种分页查询方式详解(全网最全)

一、分页查询基础概念与原理1.1什么是分页查询分页查询是指将大量数据分割成多个小块(页)进行展示的技术,它是现代Web应用中必不可少的功能。想象一下你去图书馆找书,如果所有书都堆在一张桌子上,你很难...

《战场兄弟》全事件攻略 一般事件合同事件红装及隐藏职业攻略

《战场兄弟》全事件攻略,一般事件合同事件红装及隐藏职业攻略。《战场兄弟》事件奖励,事件条件。《战场兄弟》是OverhypeStudios制作发行的一款由xcom和桌游为灵感来源,以中世纪、低魔奇幻为...

LoadRunner(loadrunner录制不到脚本)

一、核心组件与工作流程LoadRunner性能测试工具-并发测试-正版软件下载-使用教程-价格-官方代理商的架构围绕三大核心组件构建,形成完整测试闭环:VirtualUserGenerator(...

Redis数据类型介绍(redis 数据类型)

介绍Redis支持五种数据类型:String(字符串),Hash(哈希),List(列表),Set(集合)及Zset(sortedset:有序集合)。1、字符串类型概述1.1、数据类型Redis支持...

RMAN备份监控及优化总结(rman备份原理)

今天主要介绍一下如何对RMAN备份监控及优化,这里就不讲rman备份的一些原理了,仅供参考。一、监控RMAN备份1、确定备份源与备份设备的最大速度从磁盘读的速度和磁带写的带度、备份的速度不可能超出这两...

取消回复欢迎 发表评论: