技术分享 | MySQL:count(*)、count(字段) 实现上区别

作者:高鹏
文章末尾有他著作的《深入理解 MySQL 主从原理 32 讲》 , 深入透彻理解 MySQL 主从 , GTID 相关技术知识 。
继上一篇文章:
MySQL:查询字段数量多少对查询效率的影响
我们继续来讨论一下 count(*)、count(字段)实现上的区别 。 注意我们这里都使用 Innodb 做为存储引擎 , 不讨论其他引擎 。 因为了有了前面的讨论 , 更容易看出它们的区别 , 这里我们有如下注意点:

  • 我们需要做到执行计划一样 , 这里以全表扫描为例 。 实际上 count 很可能使用到覆盖索引(Using index) , 本文主要讨论它们实现的异同 。
  • count(*) 和 count(字段) 在结果上可能并不一致 。 比如 count(字段) , 但是某些行本字段为 NULL 的话那么将不会统计行数 , 下面将会说明这种 NULL 判断的位置 。 本文还是使用简单的全表扫描来进行对比实现上的区别 。 首先我们要明确的是 count 使用的是一个 COUNT 计数器 。
一、使用示例在示例中我们也可以看到两个语句的结果实际上并不一致 。
mysql> show create tablebaguai_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(*) frombaguai_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) frombaguai_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(*) frombaguai_f where b='g';+----------+| count(*) |+----------+|3 |+----------+1 row in set (0.00 sec)mysql>select count(c) frombaguai_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' 这个条件进行过滤 。
【技术分享 | MySQL:count(*)、count(字段) 实现上区别】5. MySQL 层 过滤后做一个 COUNT 计数操作
对于普通的 select 语句过滤后的数据就可以返回了 , 但是对于 count 这种操作 , 这里做的是一个计数操作 , 其中行会对 count 字段的 NULL 值进行判断 , 当然这里是 count(*) 也就不存在 NULL 值判断了 , 下面是这段代码: