千家信息网

MySQL:count(*) count(字段) 实现上区别

发表于:2026-11-01 作者:千家信息网编辑
千家信息网最后更新 2026年11月01日,水平有限如有误请指出源码版本:5.7.22欢迎关注我的《深入理解MySQL主从原理 32讲 》,如下:如果图片不能显示可查看下面链接:https://www.jianshu.com/p/d636215
千家信息网最后更新 2026年11月01日MySQL:count(*) count(字段) 实现上区别

水平有限如有误请指出
源码版本:5.7.22
欢迎关注我的《深入理解MySQL主从原理 32讲 》,如下:


如果图片不能显示可查看下面链接:

https://www.jianshu.com/p/d636215d767f

继上一篇文章:
https://www.jianshu.com/p/ce063e2024ad MySQL:查询字段数量多少对查询效率的影响

我们继续来讨论一下count(*) count(字段) 实现上的区别。注意我们这里都使用Innodb做为存储引擎,不讨论其他引擎。因为了有了前面的讨论,更容易看出它们的区别,这里我们有如下注意点:

  • 我们需要做到执行计划一样,这里一全表扫描为例。实际上count 很可能使用到覆盖索引(Using index),本文主要讨论它们实现的异同。
  • count(*)和count(字段) 在结果上可能并不一致。比如count(字段),但是某些行本字段为NULL的话那么将不会统计行数,下面将会说明这种NULL判断的位置。

本文还是使用简单的全表扫描来进行对比实现上的区别。首先我们要明确的是count使用的是一个COUNT计数器。

一、使用示例

在示例中我们也可以看到两个语句的结果实际上并不一致

mysql> show create table  baguai_f  \G*************************** 1. row ***************************       Table: baguai_fCreate Table: CREATE TABLE `baguai_f` (  `id` int(11) DEFAULT NULL,  `a` varchar(20) DEFAULT NULL,  `b` varchar(20) DEFAULT NULL,  `c` varchar(20) DEFAULT NULL) ENGINE=InnoDB DEFAULT CHARSET=utf81 row in set (0.00 sec)mysql> select * from baguai_f  ;+------+------+------+------+| id   | a    | b    | c    |+------+------+------+------+|    1 | g    | g    | NULL ||    1 | g1   | g1   | g1   ||    3 | g2   | g2   | g2   ||    4 | g    | g    | NULL ||    5 | g    | g    | NULL ||    6 | g3   | g3   | g3   |+------+------+------+------+6 rows in set (0.00 sec)mysql> desc select count(*) from  baguai_f where b='g';+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+| id | select_type | table    | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+|  1 | SIMPLE      | baguai_f | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    6 |    16.67 | Using where |+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+1 row in set, 1 warning (0.00 sec)mysql> desc select count(c) from  baguai_f where b='g';+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+| id | select_type | table    | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+|  1 | SIMPLE      | baguai_f | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    6 |    16.67 | Using where |+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+1 row in set, 1 warning (0.00 sec)mysql>  select count(*) from  baguai_f where b='g';+----------+| count(*) |+----------+|        3 |+----------+1 row in set (0.00 sec)mysql>  select count(c) from  baguai_f where b='g';+----------+| count(c) |+----------+|        0 |+----------+1 row in set (0.00 sec)

这种不一致来自于b='g'的c列中 都是NULL值,因此count(c)返回为0。

二、示例中 count(*) 获取数据流程简析

注意在《MySQL:查询字段数量多少对查询效率的影响》一文中我们已经详细的描述了部分流程,这里不再熬述,如果需要更加详细的了解,自行参考。

1、MySQL层 构建read_set

这里构建的read_set实际上只会包含列b,即一个字段。

2、Innodb层 构建模板

同理根据read_set构建的字段模板中只会包含列b。

LOOP:这里开始循环返回每一条数据

3、Innodb层 根据模板返回数据

这里我们可以看看模板的数量和模板对应的具体列名

  • 模板的数量
断点:row_sel_store_mysql_rec查看模板数量:(gdb) p prebuilt->n_template$1 = 1
  • 查看模板对应的字段
断点:row_sel_field_store_in_mysql_format_func查看模板对应的字段:(gdb) p field->name$3 = {m_name = 0x7ffe7c99cf85 "b"}

显然这里只是将b列的值返回给了MySQL层,这里也很好理解,因为b列在MySQL层需要继续做过滤操作。

4、MySQL层 过滤条件b='g'

好了当前返回给MySQL层的数据中只有b列的数据,然后施加b='g'这个条件进行过滤。

5、MySQL层 过滤后做一个COUNT计数操作

对于普通的select语句过滤后的数据就可以返回了,但是对于count这种操作,这里做的是一个计数操作,其中行会对count 字段的NULL值进行判断,当然这里是count(*) 也就不存在NULL值判断了,下面是这段代码:

bool Item_sum_count::add(){  if (aggr->arg_is_null(false))    return 0;  count++;  return 0;}
END LOOP

最终我们只需要返回这个计数就可以了。下面是发送的数据,断点可以设置在Query_result_send::send_data中。

$22 = Item::SUM_FUNC_ITEM(gdb) p ((Item*)(items)->first->info)->field_type()$23 = MYSQL_TYPE_LONGLONG(gdb) p ((Item*)(items)->first->info)->val_int()$24 = 3(gdb) p (items)->first->info$26 = (void *) 0x7ffe7c006580(gdb) p ((Item_sum_count*)$26)->count$28 = 3

我们可以发送的数据实际就是这个计数器,最终值为3。

三、示例中 count(c) 获取数据流程的不同

实际上整个流程基本一致,但是区别在于:

  • 构建的read_set 不同,模板个数自然不同,因为需要2个字段,即b、c两个字段,其中b列用于where条件过滤,而b列用于统计是否有NULL值,因此模板数量为2,如下:
(gdb) p prebuilt->n_template$29 = 2
  • 做COUNT计数器的时候会根据c 列的NULL值做实际的过滤,操作只要是NULL则count计数不会增加1,这个还是参考这段代码:
bool Item_sum_count::add(){  if (aggr->arg_is_null(false)) //过滤NULL值    return 0;  count++;  return 0;}

最终会调入函数Field::is_null进行NULL值判断,断点可以设置在这里。

四、不同点总结

示例中的语句count(c)返回为0。现在我们很清楚了,这些数据什么时候过滤掉的,总结如下:

  • Innodb层返回了全部的行数据。
  • MySQL层通过where条件过滤,剩下了b='g'的行。
  • MySQL层通过NULL判断,将剩下的count(c)中为NULL的行也排除在计数之外。

而count(*)则没有第3步,这是一个不同。

然后的不同点就是在返回的字段上:

  • count(c) 很明显除了where条件以外,还需要返回c列给MySQL层
  • count(*) 则不需要返回额外的字段给MySQL层,只需要MySQL层过滤需要的b列即可。

通过上面的分析,实际上 效率没有太大的差别,我觉得同样执行计划,同样返回数据结果的前提下,可能count(*)的效率要略微高一点。

五、备用栈帧

  1. NULL值计数过滤栈帧
#0  Field::is_null (this=0x7ffe789949d8, row_offset=0) at /root/mysqlall/percona-server-locks-detail-5.7.22/sql/field.h:1129#1  0x0000000000fbc678 in Item_field::is_null (this=0x7ffe78006a78) at /root/mysqlall/percona-server-locks-detail-5.7.22/sql/item.h:2929#2  0x000000000146ebf5 in Aggregator_simple::arg_is_null (this=0x7ffe78b451d0, use_null_value=false)    at /root/mysqlall/percona-server-locks-detail-5.7.22/sql/item_sum.cc:1633#3  0x000000000146ef18 in Item_sum_count::add (this=0x7ffe780066c0) at /root/mysqlall/percona-server-locks-detail-5.7.22/sql/item_sum.cc:1683#4  0x0000000001478475 in Aggregator_simple::add (this=0x7ffe78b451d0) at /root/mysqlall/percona-server-locks-detail-5.7.22/sql/item_sum.h:682#5  0x0000000001478301 in Item_sum::aggregator_add (this=0x7ffe780066c0) at /root/mysqlall/percona-server-locks-detail-5.7.22/sql/item_sum.h:526#6  0x000000000157fd0b in update_sum_func (func_ptr=0x7ffe78007740) at /root/mysqlall/percona-server-locks-detail-5.7.22/sql/sql_executor.cc:535#7  0x0000000001585d3e in end_send_group (join=0x7ffe78007370, qep_tab=0x7ffe78007bd0, end_of_records=false)    at /root/mysqlall/percona-server-locks-detail-5.7.22/sql/sql_executor.cc:3117#8  0x0000000001582059 in evaluate_join_record (join=0x7ffe78007370, qep_tab=0x7ffe78007a58)    at /root/mysqlall/percona-server-locks-detail-5.7.22/sql/sql_executor.cc:1645#9  0x000000000158145a in sub_select (join=0x7ffe78007370, qep_tab=0x7ffe78007a58, end_of_records=false)    at /root/mysqlall/percona-server-locks-detail-5.7.22/sql/sql_executor.cc:1297#10 0x0000000001580cce in do_select (join=0x7ffe78007370) at /root/mysqlall/percona-server-locks-detail-5.7.22/sql/sql_executor.cc:950
字段 数据 模板 实际 不同 数量 实际上 条件 示例 一致 效率 断点 流程 查询 结果 计数器 语句 不同点 两个 代码 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 广东猪兼强互联网科技有限公司 互联网科技巨头监管 疫情对互联网科技公司影响 互联网科技公司发展 致骋互联网科技 电子科技大学互联网 人人互联网科技是培训 甘肃拓世互联网科技有限公司 互联网科技生活的内容 青海省互联网新科技 互联网之后的科技是什么 金融科技与互联网金融相同和不同 互联网科技与管理 昊天互联网科技有限公司 未来五年的互联网科技 迪庆互联网科技招生 互联网在线教育科技创业 沐思想互联网科技有限公司 张召忠谈中国互联网科技 科技互联网公司两年变化 中资互联网科技 盛创互联网科技有限公司 欧洲十大科技互联网公司 全球顶尖互联网科技公司 无锡工业互联网科技有限公司 信承诺互联网科技有限公司 互联网科技平台产品运营 保山互联网科技靠谱吗 陕西彩京云互联网科技有限公司 六子台科技互联网展会
0