理解 mysql 之 count(*)的性能问题

文章讨论了MySQL中InnoDB存储引擎下count(*)性能较差的原因,并提出了通过Redis缓存和二级缓存(如Caffeine)进行优化的解决方案。同时,比较了count(*)、count(1)、count(id)等不同计数方式的性能差异。

一、 count(*) 为什么性能差

在Mysql中,count()的作用是统计表中记录的总行数。而count()的性能跟存储引擎有直接关系,并非所有的存储引擎,count(*)的性能都很差。在Mysql中使用最多的存储引擎是:innodb 和 myisam 。

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

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

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

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

二、 如何优化 count(*) 性能

2.1、增加redis缓存

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

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

用户第一次访问页面时,redis中的count值设置成1。用户以后每访问一次页面,都让count加1,最后重新设置到redis中。

在这里插入图片描述
这样在需要展示数量的地方,从 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(*)的查询效率。

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

三、 使用 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(*)是最快的。

<think>嗯,用户想知道MySQLcount(*)的用法和示例。首先,我需要回忆一下count(*)的基本功能。count(*)是用来统计表中的记录数的,包括NULL值,这一点和count(列名)不同,后者会忽略NULL。不过用户可能已经知道这点,还是需要明确说明。 接下来,用户可能需要具体的例子。比如基本用法,比如统计整个表的行数。这时候应该举一个简单的SELECT COUNT(*) FROM table的例子,比如用户提供的引用中有一个例子是SELECT COUNT(*) FROM customers WHERE age > 30,结果返回500。这个可以作为一个示例,说明带条件的统计。 然后,用户可能想知道count(*)和其他聚合函数一起使用的情况。比如结合SUM或者MAX,但根据引用4,非分组查询中可以包含多个聚合函数,比如SELECT COUNT(*), SUM(sales), MAX(date) FROM sales_table。这时候需要提醒用户,如果没有GROUP BY,这些聚合函数会统计整个表的数据。 另外,用户可能对窗口函数中的count(*) over()感兴趣。引用1提到了COUNT(*) OVER()的用法,比如分区统计。比如按部门分区统计每个员工的信息,同时显示部门总人数。这时候示例代码应该是SELECT name, department, COUNT(*) OVER(PARTITION BY department) AS dept_total FROM employees。这样可以展示窗口函数在count(*)中的应用。 还要注意性能优化的问题。引用3提到count(*)count(列名)的区别,以及查询优化。比如MyISAM和InnoDB引擎下count(*)的不同表现,是否需要加WHERE条件,索引的影响等。这些可能需要详细说明,帮助用户理解如何优化count(*)查询。 用户的问题可能还涉及到count(*)count(1)的区别。根据引用2,COUNT(*)COUNT(1)在MySQL中效果相同,但需要说明这点,避免用户混淆。 另外,是否有常见的错误用法需要注意?比如在需要分组统计时忘记加GROUP BY,导致结果不正确。或者在使用窗口函数时,分区的正确使用方式。 最后,总结count(*)的使用场景,比如统计总行数、结合条件过滤、窗口函数中的分区统计等,并提醒性能优化的注意事项,如索引的使用、避免全表扫描等。 现在需要将这些点组织成结构清晰的回答,分点说明,并给出示例代码,同时引用相关的引用来源。确保每个示例正确,符合MySQL语法,并标注引用编号。最后生成相关问题,帮助用户进一步学习。</think>### MySQL中`COUNT(*)`函数的使用方法及示例 `COUNT(*)`是MySQL中用于统计**记录总数**的聚合函数,它会计算包括`NULL`值在内的所有行。以下是具体用法及场景分析: --- #### 一、基本用法 1. **统计全表记录数** 直接对表执行`COUNT(*)`,返回表中所有行的总数(含空值): ```sql SELECT COUNT(*) FROM customers; ``` 结果示例: ``` +----------+ | COUNT(*) | +----------+ | 1000 | +----------+ ``` 2. **结合条件过滤** 添加`WHERE`子句统计符合条件的记录数: ```sql SELECT COUNT(*) FROM customers WHERE age > 30; ``` 结果示例(引用自参考内容): ``` +----------+ | COUNT(*) | +----------+ | 500 | +----------+ [^2] ``` --- #### 二、与其他聚合函数联用 在非分组查询中,可同时使用多个聚合函数: ```sql SELECT COUNT(*), SUM(sales), MAX(order_date) FROM sales_table; ``` 此查询会返回总记录数、销售额总和及最新订单日期[^4]。 --- #### 三、结合窗口函数(高级用法) 通过`COUNT(*) OVER()`实现**分区统计**,例如按部门统计员工总数: ```sql SELECT name, department, COUNT(*) OVER(PARTITION BY department) AS dept_total FROM employees; ``` 结果示例: ``` +-------+------------+------------+ | name | department | dept_total | +-------+------------+------------+ | Alice | HR | 5 | | Bob | HR | 5 | | Eva | IT | 8 | +-------+------------+------------+ [^1] ``` --- #### 四、性能优化建议 1. **引擎差异** - **MyISAM**:`COUNT(*)`直接返回元数据,速度极快。 - **InnoDB**:需全表扫描或利用索引统计,建议为条件字段(如`age`)添加索引[^3]。 2. **避免全表扫描** 添加有效的`WHERE`条件或使用覆盖索引: ```sql -- 使用索引优化 CREATE INDEX idx_age ON customers(age); SELECT COUNT(*) FROM customers WHERE age > 30; -- 索引生效 ``` 3. **`COUNT(*)` vs `COUNT(列名)`** - `COUNT(*)`:统计所有行,包括`NULL`。 - `COUNT(列名)`:统计该列非`NULL`的行数[^3]。 --- #### 五、常见误区 - **`COUNT(*)`与`COUNT(1)`的区别**:在MySQL中两者性能相同,均统计所有行[^2]。 - **分组统计遗漏`GROUP BY`**:若需分组统计,需明确指定分组字段: ```sql SELECT department, COUNT(*) FROM employees GROUP BY department; ``` --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值