Skip to main content

深入探索 MySQL COUNT() 函数:源码修改实验详解

·384 words·2 mins
Author
GrokDb
A little bit about me

插图

1. 引言
#

在 SQL 中,COUNT() 函数是一个常用的聚合函数,用于计算行数。本文将通过一个源码修改实验,深入探讨 MySQL 中 COUNT() 函数的内部工作原理,特别是它对 NULL 值的处理方式。

2. COUNT() 函数的官方说明
#

MySQL 官方文档对 COUNT() 函数的描述如下:

COUNT(expr) [over_clause] Returns a count of the number of non-NULL values of expr in the rows retrieved by a SELECT statement. The result is a BIGINT value.

COUNT(*) is somewhat different in that it returns a count of the number of rows retrieved, whether or not they contain NULL values.

翻译:

COUNT(expr) 返回在 SELECT 语句检索到的行中 expr 的非 NULL 值的数量。结果是一个 BIGINT 值。

COUNT(*) 有些不同,它返回检索到的行的数量,无论这些行是否包含 NULL 值。

参考链接: MySQL 8.0 Reference Manual

3. 实验环境准备
#

3.1 创建测试表和数据
#

DROP TABLE IF EXISTS test_count;
CREATE TABLE test_count (id INT, crab VARCHAR(50));
INSERT INTO test_count (id, crab) VALUES (1, 'A'), (2, NULL), (3, 'B'), (4, NULL);

3.2 验证初始数据
#

SELECT * FROM test_count;

结果:

+------+------+
| id   | crab |
+------+------+
|    1 | A    |
|    2 | NULL |
|    3 | B    |
|    4 | NULL |
+------+------+

4. 标准行为验证
#

执行标准查询:

SELECT COUNT(*), COUNT(1), COUNT(2), COUNT(id), COUNT(crab) FROM test_count;

结果:

+----------+----------+----------+-----------+-------------+
| COUNT(*) | COUNT(1) | COUNT(2) | COUNT(id) | COUNT(crab) |
+----------+----------+----------+-----------+-------------+
|        4 |        4 |        4 |         4 |           2 |
+----------+----------+----------+-----------+-------------+

这个结果与官方文档的描述一致:COUNT(crab) 返回 2,因为它只计算非 NULL 值。

5. 源码修改实验
#

5.1 定位和修改源码
#

源码位置:/home/grok/mysql-server/sql/item_sum.cc

原始代码:

bool Item_sum_count::add() {
  assert(!m_is_window_function);
  if (aggr->arg_is_null(false)) {
    return current_thd->is_error();
  }
  count++;
  return current_thd->is_error();
}

修改后的代码(注释掉 NULL 检查):

bool Item_sum_count::add() {
  assert(!m_is_window_function);
  // if (aggr->arg_is_null(false)) {
  //   return current_thd->is_error();
  // }
  count++;
  return current_thd->is_error();
}

5.2 编译修改后的 MySQL
#

进入 MySQL 源码目录:

cd /home/grok/mysql-server/build

编译命令:

make

5.3 重启 MySQL 服务
#

关闭 MySQL:

shutdown;

启动修改后的 MySQL:

/home/grok/mysql-server/build/bin/mysqld --datadir=/home/grok/mysql-server/build/data --socket=/home/grok/mysql-server/build/data/mysql.sock

重新登录 MySQL:

mysql -u root -p --socket=/home/grok/mysql-server/build/data/mysql.sock

6. 修改后的行为验证
#

执行相同的查询:

SELECT COUNT(*), COUNT(1), COUNT(2), COUNT(id), COUNT(crab) FROM test_count;

结果:

+----------+----------+----------+-----------+-------------+
| COUNT(*) | COUNT(1) | COUNT(2) | COUNT(id) | COUNT(crab) |
+----------+----------+----------+-----------+-------------+
|        4 |        4 |        4 |         4 |           4 |
+----------+----------+----------+-----------+-------------+

现在,COUNT(crab) 也返回 4,表明它计算了所有行,包括 NULL 值。

7. 分析与讨论
#

  1. 标准行为的实现: 原始代码中,arg_is_null() 函数用于检查值是否为 NULL。如果为 NULL,该行不计入计数。

  2. 修改的影响: 通过注释掉 NULL 检查,我们使 COUNT() 函数对所有列(包括包含 NULL 值的列)的行为与 COUNT(*) 相同。

  3. 性能考虑: 这种修改可能会略微提高性能,因为它跳过了 NULL 值检查。但这种微小的性能提升通常不值得牺牲标准行为。

  4. 实际应用的影响: 这种修改改变了 SQL 标准行为,可能会导致依赖于标准 COUNT() 行为的应用程序出现错误。

  5. 替代方案: 在实际应用中,如果需要计算包括 NULL 在内的所有行,应使用 COUNT(*) 或 COUNT(COALESCE(column, 0)),而不是修改数据库内核。

8. 结论
#

这个实验深入展示了 MySQL COUNT() 函数的内部工作原理,特别是它处理 NULL 值的方式。虽然通过源码修改可以改变这一行为,但在实际应用中,应该依赖于数据库的标准功能,并通过 SQL 查询技巧来实现所需的计数行为。

这种深入底层的探索不仅有助于我们更好地理解数据库的工作原理,也提醒我们在处理类似需求时应该遵循最佳实践,优先考虑使用 SQL 标准功能而非修改数据库内核。

参考资料

MySQL源码编译和调试指南(Ubuntu 22.04.4 LTS) - 本文实验环境的搭建和源码编译过程参考了这篇指南。该指南由 GrokDB 撰写,详细介绍了如何在 Ubuntu 22.04.4 LTS 系统上编译和调试 MySQL 源码,为进行深入的 MySQL 源码研究提供了基础。