Databases 10 min read

Understanding the Implementation Differences Between COUNT(*) and COUNT(column) in MySQL

This article explains how MySQL executes COUNT(*) and COUNT(column) queries on InnoDB tables, showing example results, detailing each layer's processing steps, the handling of NULL values, and the subtle performance differences between the two forms.

Aikesheng Open Source Community
Aikesheng Open Source Community
Aikesheng Open Source Community
Understanding the Implementation Differences Between COUNT(*) and COUNT(column) in MySQL

Continuing from the previous article about how the number of selected fields affects query efficiency, this piece examines the implementation differences between COUNT(*) and COUNT(column) when using InnoDB as the storage engine.

Example usage

The following statements illustrate that the two counts can return different results:

mysql> show create table baguai_f \G
*************************** 1. row ***************************
Table: baguai_f
Create 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=utf8
1 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)

The discrepancy arises because column c contains only NULL values for rows where b='g' , so COUNT(c) excludes those rows.

Data flow for COUNT(*)

MySQL layer builds a read_set that includes only column b (the column used in the WHERE clause).

InnoDB constructs a template containing only column b .

The engine loops over each row, returning the value of b to MySQL.

MySQL applies the b='g' filter.

MySQL performs the COUNT aggregation; because COUNT(*) does not consider NULL , every filtered row increments the counter.

The relevant source code for the aggregation is:

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

Data flow for COUNT(c)

The read_set now contains two columns, b and c , because the value of c must be examined for NULL .

InnoDB builds a template with two fields.

During the loop, both columns are returned to MySQL.

MySQL filters rows with b='g' .

When aggregating, the same Item_sum_count::add() function checks arg_is_null(false) ; rows where c is NULL are skipped, resulting in a count of 0.

Thus the only real difference is the extra column read and the NULL‑check step, which makes COUNT(*) slightly more efficient under identical execution plans.

Summary of differences

InnoDB returns all rows in both cases.

MySQL filters rows by the WHERE condition.

For COUNT(c) , MySQL additionally filters out rows where c is NULL .

COUNT(*) does not need the extra column and therefore avoids one step.

Overall, the performance gap is minimal, but COUNT(*) can be marginally faster.

For readers who want a deeper dive into MySQL internals, the author recommends the book “深入理解 MySQL 主从原理 32 讲”.

Author: 高鹏 (WeChat: gp_22389860)

Community announcements

Free Mycat problem diagnosis and source‑code analysis.

Call for original technical articles on MySQL, DBLE, DTLE, with a reward of a JD e‑card and community merchandise.

Feel free to like, share, or comment on the article.

Query OptimizationInnoDBMySQLcount()Database PerformanceNull Handling
Aikesheng Open Source Community
Written by

Aikesheng Open Source Community

The Aikesheng Open Source Community provides stable, enterprise‑grade MySQL open‑source tools and services, releases a premium open‑source component each year (1024), and continuously operates and maintains them.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.