特性介绍 | MySQL select count(*) 、count(1)、count(列) 详解(1):概念及区别

本文首发于 2020-05-05 21:55:15

一、前言

从接触MySQL开始断断续续的看过一些文章,对count()操作众说纷纭,其中分歧点主要在于count(1)count(*)哪个效率高,有说count(1)count(*)快的(这种说法更普遍),有说二者一样快的。个人理解这两种行为可能适用于的是不同的版本,我只关心较新的MySQL版本是什么行为,详见下文。

二、含义

首先,先说明一下常见count()操作及含义:

count(*):计算包括NULL值在内的行数,SQL92定义的标准统计行数的语法。

count(1):计算包括NULL值在内的行数,其中的1是恒真表达式。

count(列名):计算指定列的行数,但不包含NULL值。

三、具体区别

MySQL手册中相关描述如下:

For transactional storage engines such as InnoDB, storing an exact row count is problematic. Multiple transactions may be occurring at the same time, each of which may affect the count.

InnoDB does not keep an internal count of rows in a table because concurrent transactions might “see” different numbers of rows at the same time. Consequently, SELECT COUNT(*) statements only count rows visible to the current transaction.

Prior to MySQL 5.7.18, InnoDB processes SELECT COUNT(*) statements by scanning the clustered index. As of MySQL 5.7.18, InnoDB processes SELECT COUNT(*) statements by traversing the smallest available secondary index unless an index or optimizer hint directs the optimizer to use a different index. If a secondary index is not present, the clustered index is scanned.

Processing SELECT COUNT(*) statements takes some time if index records are not entirely in the buffer pool. For a faster count, create a counter table and let your application update it according to the inserts and deletes it does. However, this method may not scale well in situations where thousands of concurrent transactions are initiating updates to the same counter table. If an approximate row count is sufficient, use SHOW TABLE STATUS.

InnoDB handles SELECT COUNT(*) and SELECT COUNT(1) operations in the same way. There is no performance difference.

For MyISAM tables, COUNT(*) is optimized to return very quickly if the SELECT retrieves from one table, no other columns are retrieved, and there is no WHERE clause. For example:

mysql> SELECT COUNT(*) FROM student;

This optimization only applies to MyISAM tables, because an exact row count is stored for this storage engine and can be accessed very quickly.COUNT(1) is only subject to the same optimization if the first column is defined as NOT NULL.

官方这段描述要点如下:

  1. InnoDB是事务引擎,支持MVCC,并发事务可能同时“看到”不同的行数,所以,InnoDB不保留表中的行数SELECT COUNT(*)语句只计算当前事务可见的行数。
  2. 在MySQL 5.7.18之前,InnoDB通过扫描聚集索引处理SELECT COUNT(*)语句。从MySQL 5.7.18开始,InnoDB通过遍历最小的可用二级索引来处理SELECT COUNT(*)语句,除非索引或优化器明确指示使用不同的索引。如果不存在二级索引,则扫描聚集索引。这样的设计单从 IO 的角度就节省了很多开销。
  3. InnoDB以同样的方式处理SELECT COUNT(*)SELECT COUNT(1)操作,没有性能差异。 因此,建议使用符合SQL标准的count(*)
  4. 对于MyISAM表,由于MyISAM引擎存储了精确的行数,因此,如果SELECT COUNT(*)语句不包含WHERE子句,则会很快返回。这个很好理解,如果带了where条件,就需要扫表了。
  5. 如果索引记录不完全在缓冲池中,则处理SELECT(*)语句需要一些时间。为了更快的计数,您可以创建一个计数器表,并让您的应用程序按插入和删除操作更新它。然而,这种方法在同一计数器表中启动成千上万个并发事务的情况下,可能无法很好地扩展。如果一个近似的行数足够,可以使用SHOW TABLE STATUS查询行数。

到这里我们明白了 count(*)count(1) 本质上面其实是一样的,那么 count(column) 又是怎么回事呢?

count(column) 也是会遍历整张表,但是不同的是它会拿到 column 的值以后判断是否为空,然后再进行累加,那么如果针对主键需要解析内容,如果是二级索引需要再次根据主键获取内容,则要多一次 IO 操作,所以 count(column) 的性能肯定不如前两者,如果按照效率比较的话:count(*)=count(1)>count(primary key)>count(非主键column)

四、建议

基于以上描述,如果要查询innodb存储引擎的表的总行数,有如下建议:

  1. 若仅仅是想获取大概的行数,建议使用show table status或查询information_schema.tables
mysql> use db6;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> show tables;
+---------------+
| Tables_in_db6 |
+---------------+
| t1            |
+---------------+
1 row in set (0.01 sec)

mysql> select count(*) from t1;
+----------+
| count(*) |
+----------+
|        2 |
+----------+
1 row in set (0.00 sec)

mysql> show table status\G
*************************** 1. row ***************************
           Name: t1
         Engine: InnoDB
        Version: 10
     Row_format: Dynamic
           Rows: 2
 Avg_row_length: 8192
    Data_length: 16384
Max_data_length: 0
   Index_length: 0
      Data_free: 0
 Auto_increment: NULL
    Create_time: 2020-04-21 12:00:44
    Update_time: NULL
     Check_time: NULL
      Collation: utf8mb4_general_ci
       Checksum: NULL
 Create_options:
        Comment:
1 row in set (0.00 sec)

mysql> select * from information_schema.tables where table_name = 't1'\G
*************************** 1. row ***************************
  TABLE_CATALOG: def
   TABLE_SCHEMA: db6
     TABLE_NAME: t1
     TABLE_TYPE: BASE TABLE
         ENGINE: InnoDB
        VERSION: 10
     ROW_FORMAT: Dynamic
     TABLE_ROWS: 2
 AVG_ROW_LENGTH: 8192
    DATA_LENGTH: 16384
MAX_DATA_LENGTH: 0
   INDEX_LENGTH: 0
      DATA_FREE: 0
 AUTO_INCREMENT: NULL
    CREATE_TIME: 2020-04-21 12:00:44
    UPDATE_TIME: NULL
     CHECK_TIME: NULL
TABLE_COLLATION: utf8mb4_general_ci
       CHECKSUM: NULL
 CREATE_OPTIONS:
  TABLE_COMMENT:
1 row in set (0.00 sec)
  1. 反之,如果必须要获取准确的总行数,建议:
  1. 创建一个计数器表,并让您的应用程序按插入和删除操作更新它。
  2. 若业务插入和删除相对较少,也可以考虑缓存到 redis。

篇幅有限,深入验证、源码分析将在下一篇文章中介绍。


欢迎关注我的微信公众号【数据库内核】:分享主流开源数据库和存储引擎相关技术。

标题网址
GitHubhttps://dbkernel.github.io
知乎https://www.zhihu.com/people/…
思否(SegmentFault)https://segmentfault.com/u/db…
掘金https://juejin.im/user/5e9d3e…
开源中国(oschina)https://my.oschina.net/dbkernel
博客园(cnblogs)https://www.cnblogs.com/dbkernel
<think>我们正在讨论SQL中count(1)count(*)的性能和区别。根据引用[1]的内容,我们可以看到count(*)count()在性能上可能有显著差异,特别是在有WHERE子句的情况下。在内存中可能只有3倍的性能差异,但在IO密集的场景下,可能有10倍甚至100倍的差异。 但是,我们这里讨论的是count(1)count(*)区别。实际上,在大多数数据库系统中,count(1)count(*)的性能是相同的,因为优化器会对它们进行相同的处理。 区别主要在于语义: - count(*) 统计的是结果集中的行数,包括所有都为NULL的行。 - count(1) 统计的是每行中常量为1的个数(即每行都满足条件,所以也是统计行数)。 - count() 统计的是该不为NULL的行数。 因此,count(*)count(1)都会返回表中的总行数,而count()则返回该非空值的行数。 关于性能: - 在大多数数据库(如MySQL, Oracle, SQL Server, PostgreSQL)中,count(*)count(1)在性能上没有区别,因为优化器会以相同的方式处理它们。 - 但是,如果使用count(),则可能会有所不同,因为数据库需要检查该是否为NULL。 引用[1]中提到的性能差异主要是针对count(*)count()的比较,而不是count(1)count(*)。 因此,总结如下: 1. 在语义上,count(*)count(1)都是统计行数,结果相同。 2. 在性能上,count(*)count(1)没有区别。 3. count()则不同,它只统计非空值,且性能可能因的类型和索引情况而异。 所以,在编写SQL时,建议使用count(*)来统计行数,因为这是最标准的写法,并且可以清晰地表达意图。 生成相关问题: 1. 在SQL中,count()count(*)有什么区别? 2. 为什么说count()的性能可能比count(*)差? 3. 在哪些情况下,count()会使用索引而count(*)不会? 4. 如何优化大数据表上的count(*)查询? 5. 在MySQL中,MyISAM和InnoDB存储引擎对于count(*)的实现有什么不同? 注意:在回答中引用了引用[1],所以我们在末尾添加引用标识[^1]。</think>### `COUNT(1)` vs `COUNT(*)` 性能与区别详解 在 SQL 中,`COUNT(1)` 和 `COUNT(*)` 都是用于统计行数的聚合函数,但存在关键差异: #### 1. **语义区别** - **`COUNT(*)`** 统计**所有行数**,包括 `NULL` 值和空行。 示例:`SELECT COUNT(*) FROM users;` → 返回总行数 - **`COUNT(1)`** 统计**常量表达式 `1` 出现的次数**(本质是每行生成一个 `1` 并计数)。 示例:`SELECT COUNT(1) FROM users;` → 结果与 `COUNT(*)` 相同 - **`COUNT()`** 统计**指定非 NULL 值的数量**(忽略 `NULL`)。 示例:`SELECT COUNT(email) FROM users;` → 只统计非空邮箱 #### 2. **性能对比** | **场景** | `COUNT(*)` | `COUNT(1)` | `COUNT()` | |------------------------|--------------------|--------------------|---------------------| | **无索引表** | 全表扫描 | 全表扫描 | 全表扫描 | | **有索引表** | 优先用索引优化 | 优先用索引优化 | 依赖索引 | | **WHERE 子句过滤** | 高效(索引利用) | 高效(索引利用) | 可能低效[^1] | | **IO 密集型负载** | 最快 | 等效 `COUNT(*)` | 慢 3-100 倍[^1] | | **结果一致性** | 总行数 | 总行数 | 非 NULL 行数 | > 💡 现代数据库优化器(如 MySQL 8.0+、Oracle、PostgreSQL)会将 `COUNT(1)` 和 `COUNT(*)` **编译为相同执行计划**,性能无差异[^1]。但 `COUNT()` 可能触发额外检查。 #### 3. **使用建议** 1. **统计总行数 → 用 `COUNT(*)`** 最标准写法,意图明确且兼容所有 SQL 标准。 ```sql -- 正确:统计订单总数 SELECT COUNT(*) FROM orders; ``` 2. **统计非 NULL 值 → 用 `COUNT()`** 需明确排除 `NULL` 时使用。 ```sql -- 统计有邮箱的用户数 SELECT COUNT(email) FROM users; ``` 3. **避免 `COUNT(1)`** 无实际优势,且可能引发混淆(新手误以为 `1` 是名)。 #### 4. **性能优化技巧** - **索引优化**:对 `WHERE` 条件建索引可加速计数 ```sql CREATE INDEX idx_status ON orders(status); SELECT COUNT(*) FROM orders WHERE status = 'shipped'; -- 索引加速 ``` - **近似计数**(海量数据): ```sql -- PostgreSQL 快速估算 SELECT reltuples FROM pg_class WHERE relname = 'orders'; ``` - **避免全表扫描**: 添加有效过滤条件减少扫描范围。 --- ### 总结 | **函数** | 适用场景 | 性能 | 推荐度 | |--------------|-----------------|-----------|-------| | `COUNT(*)` | 精确统计总行数 | ⭐⭐⭐⭐⭐ | ✅✅✅ | | `COUNT()` | 统计非 NULL 值 | ⭐⭐ (可能慢) | ✅✅ | | `COUNT(1)` | 等效 `COUNT(*)` | ⭐⭐⭐⭐⭐ | ⚠️ 避免 | > 📌 **结论**:**优先使用 `COUNT(*)`**,既符合 SQL 标准又无性能损失。仅在需排除 `NULL` 时用 `COUNT()`,避免使用 `COUNT(1)`。 --- ### 相关问题 1. 为什么 `COUNT()` 在 WHERE 子句存在时性能可能下降? 2. 如何优化千万级数据表的 `COUNT(*)` 查询速度? 3. 在哪些场景下 `COUNT()` 的结果会小于 `COUNT(*)`? 4. MySQL 中 MyISAM 和 InnoDB 引擎对 `COUNT(*)` 的实现有何不同? 5. 如何用执行计划(EXPLAIN)验证 `COUNT` 函数的索引使用情况? [^1]: 引用参考
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值