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. 分析与讨论 #
-
标准行为的实现: 原始代码中,
arg_is_null()
函数用于检查值是否为 NULL。如果为 NULL,该行不计入计数。 -
修改的影响: 通过注释掉 NULL 检查,我们使 COUNT() 函数对所有列(包括包含 NULL 值的列)的行为与 COUNT(*) 相同。
-
性能考虑: 这种修改可能会略微提高性能,因为它跳过了 NULL 值检查。但这种微小的性能提升通常不值得牺牲标准行为。
-
实际应用的影响: 这种修改改变了 SQL 标准行为,可能会导致依赖于标准 COUNT() 行为的应用程序出现错误。
-
替代方案: 在实际应用中,如果需要计算包括 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 源码研究提供了基础。