当前位置: 首页 > article >正文

MySQL中的count函数

1. COUNT() 是什么?

        在 MySQL 中,COUNT() 是一个聚合函数,用于统计结果集中行的数量。它常见的几种用法包括:

  • COUNT(*):统计结果集中所有行的数量,包括包含 NULL 的行。
  • COUNT(1):统计结果集中所有行的数量,和 COUNT(*) 功能相同。
  • COUNT(字段名):统计结果集中某个字段非 NULL 值的数量。
  • COUNT(主键字段名):统计结果集中某个主键字段非 NULL 值的数量。

简单例子

假设有一个 users 表,数据如下:

idnameage
1Alice25
2BobNULL
3Charlie30
NULLNULL20
  1. COUNT(*)

    SELECT COUNT(*) FROM users;

    结果:4(统计所有行,无论字段是否为 NULL)。

  2. COUNT(id)

    SELECT COUNT(id) FROM users;

    结果:3(统计 id 列非 NULL 值的数量)。

  3. COUNT(DISTINCT age)

    SELECT COUNT(DISTINCT age) FROM users;

    结果:3(去重后的 age 值:25, 30, 20)。

先给结论:

执行效率排序(InnoDB)

方法功能执行过程性能情况
COUNT(*)统计所有行的数量(包括 NULL 行)遍历表或索引,计算所有行数,InnoDB 遍历聚簇索引最高效率,InnoDB 会通过聚簇索引快速扫描
COUNT(1)统计所有行的数量优化器会将其转换为 COUNT(*),功能和过程完全相同COUNT(*) 相同,性能无差异
COUNT(主键字段)统计所有行的数量(主键字段非 NULL通过主键索引扫描,所有主键字段值非 NULL高效,MySQL 会直接使用主键索引进行扫描
COUNT(字段)统计指定字段非 NULL 的行数如果字段有索引,使用索引扫描;没有索引则需要全表扫描如果字段有索引,效率较高;无索引时性能较差

具体原因解释:

  1. COUNT(*)

    • COUNT(*) 的效率在 InnoDB 中通常最高,因为它会遍历整个表或索引计算所有行数。对于 InnoDB,它通常依赖于聚簇索引来获取表的行数,聚簇索引直接将表数据存储在索引叶节点中,避免了额外的查找开销,因此相对高效。
  2. COUNT(1)

    • COUNT(1) 实际上和 COUNT(*) 完全等效。因为 1 是一个常量,不涉及任何字段,MySQL 会优化 COUNT(1)COUNT(*),两者的执行过程是一样的。所以,性能与 COUNT(*) 相同。
  3. COUNT(主键字段)

    • 由于 InnoDB 使用聚簇索引,主键索引包含了表的所有行数据。如果你使用主键字段来计数,MySQL 会利用主键索引来扫描行。相比全表扫描,主键索引扫描通常更高效。因此,COUNT(主键字段) 在 InnoDB 中通常比 COUNT(字段) 更高效。
  4. COUNT(字段)

    • 有索引的字段:如果字段有索引,MySQL 会直接扫描索引来计算非 NULL 的行数,效率较高。
    • 没有索引的字段:如果字段没有索引,MySQL 会进行全表扫描,逐行检查字段值是否为 NULL,性能较差。

总结

对于 InnoDB 引擎:

  • COUNT(*)COUNT(1) 的执行效率是相同的,通常效率最高。
  • COUNT(主键字段) 依赖于主键索引,通常效率也很高,尤其当主键索引可用时。
  • COUNT(字段) 的性能取决于字段是否有索引。如果字段没有索引,效率最低,因为需要全表扫描。

性能排序(InnoDB)

COUNT(*) = COUNT(1) > COUNT(主键字段) > COUNT(字段)

2. COUNT(字段) 的执行过程

什么是 COUNT(字段)

COUNT(字段) 用于统计结果集中某个字段值不为 NULL 的行数。
它与 COUNT(*)COUNT(1) 不同,不会统计字段值为 NULL 的行。

COUNT(字段) 的执行流程

假设我们使用以下表和数据:

idnameage
1Alice25
2BobNULL
3Charlie30
NULLNULL20

执行查询:

SELECT COUNT(age) FROM users;
  1. 全表扫描

    • MySQL 遍历表中每一行。
  2. 字段值检查

    • 对于 age 字段,MySQL 检查其值是否为 NULL
    • 如果字段值不为 NULL,计数器加一;如果字段值为 NULL,则跳过。
  3. 结果返回

    • 扫描完成后,计数器的值即为 COUNT(age) 的结果。

对于以上数据,COUNT(age) 的结果是 3,因为 age 字段有 3 行值非 NULL(25、30、20)。

COUNT(字段) 的注意点

  1. COUNT(*)COUNT(字段) 的区别

    • COUNT(*):统计所有行,包括字段值为 NULL 的行。
    • COUNT(字段):只统计字段值非 NULL 的行。

    示例:

    SELECT COUNT(*), COUNT(age) FROM users;

    返回结果:

    COUNT(*)COUNT(age)
    43
    • COUNT(*) 是 4:表中有 4 行。
    • COUNT(age) 是 3age 字段中有 1 个 NULL 值。
  2. 索引的优化

    • 如果字段上存在索引,MySQL 可以直接扫描索引,而无需全表扫描。
    • 对于非索引字段,MySQL 仍需要逐行检查字段值是否为 NULL

小结

  • COUNT(字段) 统计指定字段值不为 NULL 的行数。
  • 它需要逐行检查字段值是否为 NULL,并根据条件增加计数器。
  • 如果表中字段的 NULL 比例较高,COUNT(字段) 的结果可能显著小于 COUNT(*)

3. COUNT(主键字段) 的执行过程

什么是主键字段?

        在 MySQL 中,主键(Primary Key)是表中唯一标识每一行的列或列的组合,它具有以下特点:

  • 每个主键值唯一。
  • 主键列不能为 NULL

假设我们有以下表结构和数据:

CREATE TABLE users ( id INT PRIMARY KEY, name VARCHAR(50), age INT ); 
INSERT INTO users VALUES (1, 'Alice', 25), (2, 'Bob', NULL), (3, 'Charlie', 30), (4, NULL, 20);

数据如下:

idnameage
1Alice25
2BobNULL
3Charlie30
4NULL20

执行过程:COUNT(id)

SELECT COUNT(id) FROM users;

在执行 COUNT(主键字段) 时,MySQL 的执行过程如下:

  1. 索引查找
    主键字段 id 是一个索引(通常是聚簇索引),因此 MySQL 首先扫描主键索引。

  2. 非空检查
    COUNT(id) 只统计 id 列中非 NULL 的行。因为主键不允许为 NULL,所以表中的所有行都会被计入。

  3. 计数
    每找到一个非 NULL 值,就将计数器加一。

  4. 返回结果
    遍历所有主键后,MySQL 将计数结果返回。

在这个例子中,COUNT(id) 的结果是 4,因为表中每一行的 id 都是非空值。

注意事项

  • 如果表的主键列中所有行都非空(通常是这种情况),那么 COUNT(主键字段) 的结果与 COUNT(*) 的结果相同,但实现方式略有不同(COUNT(*) 包括扫描全表)。

4. COUNT(*) 的执行过程

COUNT(*) 是什么?

  • COUNT(*) 用于统计结果集中所有行的数量,包括 NULL 和非 NULL 值。
  • COUNT(字段名) 不同,它并不关心具体字段的值,只统计表中实际存在的行。

执行过程:COUNT(*)

假设我们有如下表和数据:

idnameage
1Alice25
2BobNULL
3Charlie30
NULLNULL20

SQL 查询:

SELECT COUNT(*) FROM users;
  1. 全表扫描

    • MySQL 对表中的每一行进行扫描,无论行中是否存在 NULL 值,所有行都会被计入。
    • 如果表使用的是 MyISAM 存储引擎,MySQL 会直接读取存储的表行数(更高效);而 InnoDB 引擎则需要遍历表或索引。
  2. 行统计

    • 每遍历一行,计数器加一。
  3. 结果返回

    • 扫描完成后,计数器的最终值即为 COUNT(*) 的结果。

对于以上数据,COUNT(*) 返回的结果是 4,因为表中有 4 行数据。

COUNT(*)COUNT(字段名) 的对比

  • COUNT(*):统计表中所有行,包含 NULL 值。
  • COUNT(字段名):只统计指定字段中非 NULL 的行。

例如:

SELECT COUNT(*), COUNT(name) FROM users;

结果为:

COUNT(*)COUNT(name)
43
  • COUNT(*) 是 4:表中有 4 行。
  • COUNT(name) 是 3name 列中有 1 个 NULL,只统计了非 NULL 的 3 行。

5. COUNT(1) 的执行过程

什么是 COUNT(1)

COUNT(1) 是一种特殊用法,其中 1 并不是表中的列,而是一个常量。
其功能与 COUNT(*) 类似,都用于统计结果集中的行数。

COUNT(1) 的执行流程

假设我们仍然使用以下表和数据:

idnameage
1Alice25
2BobNULL
3Charlie30
NULLNULL20

执行查询:

SELECT COUNT(1) FROM users;
  1. 常量优化

    • 在 SQL 查询优化阶段,MySQL 知道 1 是一个常量,与表中的任何列无关。
    • 它的作用相当于告诉 MySQL:每行都加一个计数,忽略表中的实际列值。
  2. 全表扫描

    • MySQL 扫描表中的所有行,无论是否存在 NULL 值。
  3. 计数

    • 遍历时,对每行都增加计数,无需判断任何字段是否为 NULL
  4. 结果返回

    • 扫描完成后,返回计数结果。

COUNT(1)COUNT(*) 的区别

  1. 功能上

    • 两者完全相同,都会统计结果集中所有的行。
    • 不会因为表中存在 NULL 或其他字段值的不同而有差异。
  2. 性能上

    • MySQL 优化器会将 COUNT(1) 转换为 COUNT(*)
    • 对于 MyISAM 和 InnoDB 引擎,COUNT(1)COUNT(*) 的性能是一样的。
    • 在某些场景中,COUNT(1) 会通过主键或索引更高效地完成计数,但现代 MySQL 的优化器已经能很好地处理两种情况,几乎没有差异。

小结

  • COUNT(*) 是标准写法,语义清晰,通常推荐使用。
  • COUNT(1) 功能完全相同,适合某些开发习惯或历史原因下的场景。
  • 两者性能几乎没有区别,优化器会对它们进行相同的处理。

6. 为什么通过遍历的方式来计数?

原因分析

        MySQL 的 COUNT() 函数需要准确统计行的数量或字段的非 NULL 数量,这通常需要遍历表中的数据。以下是核心原因:

1. 数据的动态性

        数据库中的数据是动态的,可能随时发生插入、更新或删除。如果 MySQL 不实时遍历表中的数据,可能导致计数结果不准确。尤其是对于 InnoDB 引擎,它没有直接存储精确的行数。

  • InnoDB 的特点

    • 数据存储在聚簇索引中,没有单独的计数记录。
    • 每次执行 COUNT(*)COUNT(字段名),都需要遍历表或索引,确保结果最新。
  • MyISAM 的优化

    • MyISAM 存储引擎会维护一个精确的行数(元数据),执行 COUNT(*) 时可以直接返回行数,而无需遍历。

2. 数据过滤的需要

  • 对于 COUNT(字段名)COUNT(DISTINCT 字段名),需要对数据进行过滤(如排除 NULL 或去重)。
  • MySQL 必须逐行检查数据,判断是否满足计数条件,因此需要遍历数据。

3. 数据分布复杂性

在实际应用中,表可能包含以下情况:

  • 稀疏数据:某些列可能大量为 NULL
  • 非连续主键:主键可能跳跃性增减。
  • 复杂条件:如果查询包含 WHERE 子句(例如 COUNT(*) WHERE age > 20),MySQL 需要根据条件筛选行,因此无法简单依赖已有的统计值。

这些复杂性决定了计数必须遍历表或索引,而无法直接通过元数据实现。

4. 索引的作用

遍历的效率可以通过索引优化。举例:

  • 对于 COUNT(主键字段),MySQL 只需要遍历聚簇索引,因为主键总是唯一且非空。
  • 对于 COUNT(*),如果表中存在合适的覆盖索引,MySQL 也可以通过索引完成统计,而无需扫描整个表。

结论

        遍历表是确保计数准确的核心方式。虽然 MyISAM 引擎通过存储行数可以省去遍历过程,但 InnoDB 的动态数据和多种计数条件决定了遍历是必要的。

7. 如何优化 COUNT(*)

        由于 COUNT(*) 通常需要遍历表或索引,这可能导致性能瓶颈,尤其是当表非常大时。以下是两种主要的优化方法:

方法一:近似值方法

核心思想

        通过统计表的一部分数据,推测出总行数,而不需要精确遍历所有行。

适用场景

  • 对计数结果的要求不是严格的精确值,而是大致估算。
  • 适用于大数据量的表,统计结果不用于事务性场景。

实现方式

  1. 采样统计

    • 从表中抽取一定比例的数据样本,计算样本的行数,然后根据比例推算总行数。
    • 例如:
      SELECT COUNT(*) * 10 AS estimated_count FROM (SELECT * FROM users LIMIT 100) AS sample;
      上例中,从表中抽取 100 行样本,假设表的总行数为 1000,则近似估算总行数为 100 × 10 = 1000
  2. 利用信息_schema 表

    • MySQL 的 information_schema.tables 中存储了表的行数估算值,但对 InnoDB 引擎来说,这个值并非实时精确。
    • 示例:
      SELECT TABLE_ROWS FROM information_schema.tables WHERE TABLE_SCHEMA = 'your_database' AND TABLE_NAME = 'your_table';

       

    • 优点:快速返回行数近似值。
    • 缺点:可能与实际行数有偏差。

优点和缺点

  • 优点:性能非常高,适合对性能敏感但允许一定误差的场景。
  • 缺点:统计结果不够精确,无法满足对数据精确度要求高的场景。

方法二:额外表保存计数值

核心思想

通过维护一个额外的计数表或计数字段,实时存储行的数量。每次插入、更新或删除操作时,自动更新计数值。

适用场景

  • 对计数值的精确性要求较高。
  • 表的更新频率较低(更新频繁时维护计数值的开销较大)。

实现方式

  1. 创建计数表或计数字段

    • 创建一张专门的计数表:
      CREATE TABLE table_counts ( table_name VARCHAR(50) PRIMARY KEY, row_count INT NOT NULL );

       

    • 在表更新时维护计数,例如通过触发器:
      CREATE TRIGGER after_insert_users AFTER INSERT ON users FOR EACH ROW BEGIN UPDATE table_counts SET row_count = row_count + 1 WHERE table_name = 'users'; END;

       

  2. 直接为目标表添加计数字段

    • 在表中增加一个字段 row_count,每次插入或删除时,手动更新这个字段。

优点和缺点

  • 优点:能够精确统计行数,查询时性能极高(无需遍历表)。
  • 缺点:需要额外的存储空间和维护开销;对频繁更新的表可能增加性能负担。

总结

  • 近似值方法 更适合追求性能但对精度要求不高的场景,例如数据分析中的大表。
  • 额外表保存计数值 更适合小表或对计数精度要求高的业务系统。

 


http://www.kler.cn/a/419118.html

相关文章:

  • C# 2024年Visual Studio实用插件集合
  • Java 解析离线 MySQL binlog 文件
  • 【css实现收货地址下边的平行四边形彩色线条】
  • 《运放秘籍》第二部:仪表放大器专项知识点总结
  • 把当下的快乐和长远的目标连接在一起。
  • Linux虚拟机安装nginx踩坑记录
  • 大模型开发和微调工具Llama-Factory-->量化2(AQLM和OFTQ)
  • Ubuntu在NVME硬盘使用Systemback安装记录
  • Design Linear Filters in the Frequency Domain (MATLAB帮助文档)
  • Python json 序列化
  • mongodb/redis/neo4j 如何自己打造一个 web 数据库可视化客户端?
  • Linux —— 《线程控制》
  • nmap基本用法
  • 【小白学机器学习39】如何用numpy生成总体,生成样本samples
  • 【RISC-V CPU debug 专栏 2.3 -- Run Control】
  • .NET周刊【11月第4期 2024-11-24】
  • React与Ant Design入门指南
  • springboot336社区物资交易互助平台pf(论文+源码)_kaic
  • Linux命令进阶·如何切换root以及回退、sudo命令、用户/用户组管理,以及解决创建用户不显示问题和Ubuntu不显示用户名只显示“$“符号问题
  • 桶排序(代码+注释)
  • webUI自动化(十)iframe切换
  • 【docker集群应用】Docker数据管理与镜像创建
  • Flutter:encrypt插件 AES加密处理
  • 10.请求拦截和响应拦截
  • Rust代写 OCaml代做 Go R语言 SML Haskell Prolog DrRacket Lisp
  • Jackson库--ObjecMapper