【MySQL进阶】count(*) 、count(1)、count(字段)到底有什么区别?哪种性能好?

本文详细探讨了MySQL中count(*)、count(1)和count(字段)的执行原理及效率差异,指出count(1)在有二级索引时性能最优,建议为统计字段建立索引以提升效率,并介绍了优化count(*)的方法,如近似值估算和计数表维护。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

【MySQL进阶】count(*) 、count(1)、count(字段)到底有什么区别?哪种性能好?


当我们对一张数据表中的记录进行统计的时候,习惯都会使用 count 函数来统计,但是 count 函数传入的参数有很多种,比如 count(1)、count( *)、count(字段) 等。

到底哪种效率是最好的呢?是不是 count(*) 效率最差?

一:哪种 count 性能最好?

我先直接说结论:

图片

要弄明白这个,我们得要深入 count 的原理,以下内容基于常用的 innodb 存储引擎来说明。

count() 是什么?

count() 是一个聚合函数,函数的参数不仅可以是字段名,也可以是其他任意表达式,该函数作用是统计符合查询条件的记录中,函数指定的参数不为 NULL 的记录有多少个

假设 count() 函数的参数是字段名,如下:

select count(name) from t_order;

这条语句是统计「 t_order 表中,name 字段不为 NULL 的记录」有多少个。也就是说,如果某一条记录中的 name 字段的值为 NULL,则就不会被统计进去。

再来假设 count() 函数的参数是数字 1 这个表达式,如下:

select count(1) from t_order;

这条语句是统计「 t_order 表中,1 这个表达式不为 NULL 的记录」有多少个。

1 这个表达式就是单纯数字,它永远都不是 NULL,所以上面这条语句,其实是在统计 t_order 表中有多少个记录。

count(主键字段) 执行过程是怎样的?

在通过 count 函数统计有多少个记录时,MySQL 的 server 层会维护一个名叫 count 的变量。

server 层会循环向 InnoDB 读取一条记录,如果 count 函数指定的参数不为 NULL,那么就会将变量 count 加 1,直到符合查询的全部记录被读完,就退出循环。最后将 count 变量的值发送给客户端。

InnoDB 是通过 B+ 树来保存记录的,根据索引的类型又分为聚簇索引和二级索引,它们区别在于,聚簇索引的叶子节点存放的是实际数据,而二级索引的叶子节点存放的是主键值,而不是实际数据。

用下面这条语句作为例子:

//id 为主键值
select count(id) from t_order;

如果表里只有主键索引,没有二级索引时,那么,InnoDB 循环遍历聚簇索引,将读取到的记录返回给 server 层,然后读取记录中的 id 值,就会 id 值判断是否为 NULL,如果不为 NULL,就将 count 变量加 1。

图片

但是,如果表里有二级索引时,InnoDB 循环遍历的对象就不是聚簇索引,而是二级索引。

图片

这是因为相同数量的二级索引记录可以比聚簇索引记录占用更少的存储空间,所以二级索引树比聚簇索引树小,这样遍历二级索引的 I/O 成本比遍历聚簇索引的 I/O 成本小,因此「优化器」优先选择的是二级索引。

count(1) 执行过程是怎样的?

用下面这条语句作为例子:

select count(1) from t_order;

如果表里只有主键索引,没有二级索引时。

图片

那么,InnoDB 循环遍历聚簇索引(主键索引),将读取到的记录返回给 server 层,但是不会读取记录中的任何字段的值,因为 count 函数的参数是 1,不是字段,所以不需要读取记录中的字段值。参数 1 很明显并不是 NULL,因此 server 层每从 InnoDB 读取到一条记录,就将 count 变量加 1。

可以看到,count(1) 相比 count(主键字段) 少一个步骤,就是不需要读取记录中的字段值,所以通常会说 count(1) 执行效率会比 count(主键字段) 高一点。

但是,如果表里有二级索引时,InnoDB 循环遍历的对象就二级索引了。

图片

此条语句在此例中用到的并不是主键索引,而是辅助索引,实际上在此例中我试验了,不管是 COUNT(1),还是 COUNT(*),MySQL 都会用成本最小的辅助索引查询方式来计数

count(*) 执行过程是怎样的?

看到 * 这个字符的时候,是不是大家觉得是读取记录中的所有字段值?

对于 selete * 这条语句来说是这个意思,但是在 count(*) 中并不是这个意思。

count(\*) 其实等于 count(0),也就是说,当你使用 count(*) 时,MySQL 会将 * 参数转化为参数 0 来处理。

图片

所以,count(*) 执行过程跟 count(1) 执行过程基本一样的,性能没有什么差异。

在 MySQL 5.7 的官方手册中有这么一句话:

InnoDB handles SELECT COUNT(\*) and SELECT COUNT(1) operations in the same way. There is no performance difference.

翻译:InnoDB以相同的方式处理SELECT COUNT(\*)和SELECT COUNT(1)操作,没有性能差异。

而且 MySQL 会对 count(*) 和 count(1) 有个优化,如果有多个二级索引的时候,优化器会使用key_len 最小的二级索引进行扫描。

只有当没有二级索引的时候,才会采用主键索引来进行统计。

count(字段) 执行过程是怎样的?

count(字段) 的执行效率相比前面的 count(1)、 count(*)、 count(主键字段) 执行效率是最差的。

用下面这条语句作为例子:

// name不是索引,普通字段
select count(name) from t_order;

对于这个查询来说,会采用全表扫描的方式来计数,所以它的执行效率是比较差的。

图片

小结

count(1)、 count(*)、 count(主键字段)在执行的时候,如果表里存在二级索引,优化器就会选择二级索引进行扫描。

所以,如果要执行 count(1)、 count(*)、 count(主键字段) 时,尽量在数据表上建立二级索引,这样优化器会自动采用 key_len 最小的二级索引进行扫描,相比于扫描主键索引效率会高一些。

再来,就是不要使用 count(字段) 来统计记录个数,因为它的效率是最差的,会采用全表扫描的方式来统计。如果你非要统计表中该字段不为 NULL 的记录个数,建议给这个字段建立一个二级索引。

二:为什么要通过遍历的方式来计数?

你可能会好奇,为什么 count 函数需要通过遍历的方式来统计记录个数?

我前面将的案例都是基于 Innodb 存储引擎来说明的,但是在 MyISAM 存储引擎里,执行 count 函数的方式是不一样的,通常在没有任何查询条件下的 count(*),MyISAM 的查询速度要明显快于 InnoDB。

使用 MyISAM 引擎时,执行 count 函数只需要 O(1 )复杂度,这是因为每张 MyISAM 的数据表都有一个 meta 信息有存储了row_count值,由表级锁保证一致性,所以直接读取 row_count 值就是 count 函数的执行结果。

而 InnoDB 存储引擎是支持事务的,同一个时刻的多个查询,由于多版本并发控制(MVCC)的原因,InnoDB 表“应该返回多少行”也是不确定的,所以无法像 MyISAM一样,只维护一个 row_count 变量。

举个例子,假设表 t_order 有 100 条记录,现在有两个会话并行以下语句:

图片

在会话 A 和会话 B的最后一个时刻,同时查表 t_order 的记录总个数,可以发现,显示的结果是不一样的。所以,在使用 InnoDB 存储引擎时,就需要扫描表来统计具体的记录。

而当带上 where 条件语句之后,MyISAM 跟 InnoDB 就没有区别了,它们都需要扫描表来进行记录个数的统计。

三:如何优化 count(*)?

如果对一张大表经常用 count(*) 来做统计,其实是很不好的。

比如下面我这个案例,表 t_order 共有 1200+ 万条记录,我也创建了二级索引,但是执行一次 select count(*) from t_order 要花费差不多 5 秒!

图片

面对大表的记录统计,我们有没有什么其他更好的办法呢?

近似值

如果你的业务对于统计个数不需要很精确,比如搜索引擎在搜索关键词的时候,给出的搜索结果条数是一个大概值。

图片

这时,我们就可以使用 show table status 或者 explain 命令来表进行估算。

执行 explain 命令效率是很高的,因为它并不会真正的去查询,下图中的 rows 字段值就是 explain 命令对表 t_order 记录的估算值。

图片

额外表保存计数值

如果是想精确的获取表的记录总数,我们可以将这个计数值保存到单独的一张计数表中。

当我们在数据表插入一条记录的同时,将计数表中的计数字段 + 1。也就是说,在新增和删除操作时,我们需要额外维护这个计数表。

<think>好的,用户现在想了解MySQLcount(*)count(1)count(字段)之间的区别。我需要结合之前提供的引用内容来回答这个问题,同时确保回答结构清晰,符合用户的要求。 首先,我需要回顾用户提供的引用内容,里面提到了几个关键点: 1. count(*)count(1)count(字段名)在效果和性能上的区别。 2. 引用3提到count(*)=count(1)=count(id)在效果上,都是统计总行数,不考虑是否为null,而count(字段)需要考虑是否为空。 3. 性能上,count(*)最快,其次是count(1)count(id),最后是count(字段)。 4. InnoDB和MyISAM对count(*)的优化不同,MyISAM会缓存行数,而InnoDB需要实时计算。 5. 查询过程方面,count(字段)需要读取字段值并判断是否为null,而count(*)count(1)不需要。 接下来,我需要组织这些信息,分成几个部分,比如效果区别性能区别、优化建议等。同时需要引用用户提供的引用来源,比如引用1、引用2等。 可能需要先解释每个count的作用,然后比较它们的不同之处。比如count(*)是统计所有行数,不管字段是否为null;count(1)也是统计所有行,但使用常量1count(字段)则只统计该字段不为null的行数。在性能方面,count(*)通常被认为是最优的,尤其是在InnoDB引擎下,因为优化器会进行优化。 还需要提到不同存储引擎的影响,比如MyISAM和InnoDB在处理count(*)时的差异。这可能涉及到用户提到的引用2和引用3中的内容,比如MyISAM会缓存行数,而InnoDB需要逐行计算,特别是带有where条件时。 此外,用户可能想知道为什么推荐使用count(*)而不是其他形式,以及在不同场景下的优化手段,比如使用近似值或额外建表保存计数值,这在引用2和引用5中有提到。 需要注意避免技术错误,比如确认count(1)count(*)MySQL中的实际表现是否确实相同,是否有特殊情况。根据引用内容,它们的效果相同,性能接近,但count(*)可能因为优化器的处理而更优。 最后,生成相关问题,这些问题应该基于用户提供的引用内容,比如性能比较、优化方法、存储引擎的影响等。</think>#### **MySQL中`COUNT(*)`、`COUNT(1)`和`COUNT(字段)`的区别详解** --- ### **一、效果差异** 1. **`COUNT(*)`** - **功能**:统计表中所有行数,**不关心任何字段是否为`NULL`**。 - **优化行为**:MySQL 会优先选择最小的非聚簇索引(如果存在)进行快速统计[^2][^5]。 - **示例**:`SELECT COUNT(*) FROM users;`(统计所有用户记录数) 2. **`COUNT(1)`** - **功能**:与`COUNT(*)`完全等价,统计所有行数。`1`是常量表达式,不会检查字段值[^2][^3]。 - **示例**:`SELECT COUNT(1) FROM users;` 3. **`COUNT(字段)`** - **功能**:统计**该字段不为`NULL`的行数**。若字段允许为`NULL`,则需逐行判断值是否非空[^1][^3]。 - **示例**:`SELECT COUNT(name) FROM users;`(统计`name`非空的用户数) --- ### **二、性能对比** 根据存储引擎和字段类型,性能排序为: $$ \text{COUNT(*)COUNT(1) > COUNT(主键) > COUNT(非索引字段)} $$ | **场景** | **性能分析** | |------------------------|----------------------------------------------------------------------------| | **MyISAM 引擎** | `COUNT(*)` 直接返回缓存的行数,时间复杂度 $O(1)$[^5]。 | | **InnoDB 引擎** | 需遍历表或最小的二级索引,时间复杂度 $O(n)$[^2]。 | | **`COUNT(非索引字段)`** | 需回表读取字段值并判断是否为`NULL`,性能最差[^1][^4]。 | --- ### **三、底层实现机制** 1. **`COUNT(*)`和`COUNT(1)`的优化** - InnoDB 优化器优先选择占用空间最小的索引(如二级索引)统计行数,避免全表扫描[^2][^5]。 - **代码模拟逻辑**: ```sql -- 假设存在二级索引 idx_age SELECT COUNT(*) FROM users USE INDEX(idx_age); ``` 2. **`COUNT(字段)`的执行过程** - **非索引字段**:需逐行读取字段值,判断是否非空(即使字段定义为`NOT NULL`,仍需校验)[^3]。 - **索引字段**:直接遍历索引树,无需回表,但需判断是否为`NULL`[^1]。 --- ### **四、常见误区与优化建议** 1. **误区澄清** - **`COUNT(*)` vs `COUNT(1)`**:两者性能几乎相同,优先使用语义更明确的`COUNT(*)`[^3][^5]。 - **`COUNT(主键)`**:若表无二级索引,需扫描聚簇索引,性能低于`COUNT(*)`[^2]。 2. **优化手段** - **大表统计**: - 使用近似值:`EXPLAIN SELECT ...` 获取估算行数[^2]。 - 维护计数表:通过触发器或定时任务更新单独的表[^2]。 - **添加条件**: ```sql SELECT COUNT(*) FROM users WHERE status = 'active'; -- 合理利用索引加速 ``` --- ### **五、存储引擎差异** | **引擎** | **`COUNT(*)`行为** | **适用场景** | |-----------|------------------------------------------------|--------------------------| | **MyISAM**| 直接返回缓存的总行数(无`WHERE`时)[^2][^5]。 | 静态表,读密集型场景。 | | **InnoDB**| 实时统计行数,受事务隔离级别影响[^2][^3]。 | 写密集型或事务型场景。 | --- #### **总结** - **效果**:`COUNT(*)`和`COUNT(1)`统计总行数;`COUNT(字段)`统计非空行数。 - **性能**:`COUNT(*)`最优,优先使用;避免`COUNT(非索引字段)`。 - **引擎优化**:MyISAM 缓存行数,InnoDB 依赖索引统计[^2]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小颜-

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值