高性能 MySQL 必备:COUNT(*)、COUNT(1)、COUNT(字段) 的选择法则

前言

在 MySQL 中统计行数是开发中最常见的需求之一,但面对 count(*)count(1)count(字段) 这些写法,很多开发者会感到困惑:它们到底有什么区别?哪种方式性能更好?如何避免踩坑?本文将通过实例来验证这些问题。


一、先看结论:它们到底有什么区别?

测试环境:MySQL 8.0.40,InnoDB 引擎

提示:使用 EXPLAIN SELECT COUNT(...) 可以查看优化器选择的执行计划。

我们从一个简单的例子开始。假设有一张用户表 user,其中包含一个可为空的字段 email

sql 复制代码
CREATE TABLE user (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(50),
    email VARCHAR(100) 
);

-- 插入测试数据
INSERT INTO user (name, email) VALUES
('张三', '[email protected]'),
('李四', NULL),
('王五', '[email protected]'),
('赵六', NULL);

执行以下查询:

sql 复制代码
SELECT COUNT(*) FROM user;       -- 结果:4(统计所有行数)
SELECT COUNT(1) FROM user;       -- 结果:4(统计所有行数)
SELECT COUNT(email) FROM user;   -- 结果:2(只统计 email 不为 NULL 的行)
SELECT COUNT(id) FROM user;      -- 结果:4(统计主键非 NULL 的行)

📌 核心区别总结

  • COUNT(*) :统计表中所有行的数量,不关心具体列的值是否为 NULL
  • COUNT(1) :与 COUNT(*) 完全等价,性能几乎无差异。
  • COUNT(字段) :只统计该字段不为 NULL 的行数。

二、性能对比:哪种写法更快?

很多人认为 COUNT(1)COUNT(*) 快,或者 COUNT(主键) 是最优选择。但实际情况如何?我们通过实验验证。

实验准备:创建百万级数据表

sql 复制代码
CREATE TABLE big_table (
    id INT PRIMARY KEY AUTO_INCREMENT,
    data VARCHAR(255),
    index idx_data (data)
);

随机生成100w 数据

测试 1:COUNT(*) vs COUNT(1)

sql 复制代码
SELECT COUNT(*) FROM big_table;

SELECT COUNT(1) FROM big_table;

执行查询结果,如下图:

结论 :两者性能几乎完全一致,MySQL 优化器对它们的处理方式相同(COUNT(*) 是 MySQL 内部优先优化的查询模式,优化程度超过 COUNT(1) ,在阅读性上也比更好些)。推荐优先使用COUNT(*)

测试 2:COUNT(*) vs COUNT(主键) vs COUNT(索引字段)

sql 复制代码
-- 使用主键索引
SELECT COUNT(id) FROM big_table;

-- 使用普通索引字段
SELECT COUNT(data) FROM big_table;

-- 全表扫描
SELECT COUNT(*) FROM big_table;

执行查询结果,如下图:

执行SQL EXPLAIN,三条SQL的结果都是一样 ,如下图:

结论 : MySQL对执行查询都有优化,查询索引优化为 (idx_data)二级索引 ,最终结果:COUNT(*) > COUNT(主键) > COUNT(索引字段)

  • COUNT(*) 性能最优 :MySQL 对 COUNT(*) 进行了特殊优化,COUNT(*) 不会真的取出所有列的数据,而是会自动选择最优的索引 进行扫描,避免不必要的 IO,同时避免了一些事务和MVCC 机制的部分检查
  • COUNT(主键)COUNT(*) 稍慢:主键索引(聚簇索引)天然包含数据行,统计时需要读取完整的索引结构(包含所有行数据),也可能MySQL可能优化为查询其他的二级索引。
  • COUNT(普通索引) 性能较慢:需要过滤非NULL值,可能需要回表。

三、使用场景:如何正确选择?

场景 1:统计所有行数(如分页总条数)

sql 复制代码
-- ✅ 推荐写法
SELECT COUNT(*) FROM user;

-- ❌ 不推荐(性能无提升,可读性略差一点)
SELECT COUNT(1) FROM user;

场景 2:统计某字段的有效值数量

sql 复制代码
-- 统计有邮箱的用户数量(自动忽略为null的数据)
SELECT COUNT(email) FROM user;  

场景 3:统计去重后的数量

sql 复制代码
-- 统计不重复的邮箱数量
SELECT COUNT(DISTINCT email) FROM user; 

四、常见误区与避坑指南

误区 1:"MyISAM 的 COUNT(*) 一定快"

虽然 MyISAM 会缓存表的总行数,但仅适用于没有 WHERE 条件的情况

sql 复制代码
-- MyISAM 引擎下,瞬间返回(缓存)
SELECT COUNT(*) FROM user;

-- 带上 WHERE 条件后,依然需要实时统计
SELECT COUNT(*) FROM user WHERE name LIKE '张%';

误区 2:"COUNT(主键) 是最优解"

通过前文的测试可见,COUNT(主键) 的性能可能不如 COUNT(*)


五、选择建议

场景 推荐写法 原因
统计所有行数 COUNT(*) 语义清晰,性能最优
统计非 NULL 值数量 COUNT(字段) 自动过滤 NULL
需要最高性能统计和具体业务字段 COUNT(索引字段) 利用索引快速统计
统计去重后的数量 COUNT(DISTINCT) DISTINCT去重

六、总结

  • COUNT(*) 是通用场景的最佳选择,既符合直觉又有良好性能。
  • 避免使用 COUNT(非索引字段),尤其是大表。
  • 不要盲目相信"经验之谈",用 EXPLAIN 分析执行计划才是王道。
相关推荐
凭君语未可5 分钟前
MySQL中的事务隔离级别有哪些
数据库·mysql
XW6 分钟前
个人图片分类-按照年分文件夹管理 python处理个人图片
后端
customer0823 分钟前
【开源免费】基于SpringBoot+Vue.JS电商应用系统(JAVA毕业设计)
java·vue.js·spring boot·后端·开源
Pandaconda24 分钟前
【后端开发面试题】每日 3 题(十五)
数据库·分布式·后端·python·面试·后端开发·幂等性
rookiefishs40 分钟前
如何nodejs中使用winston库记录本地日志?
前端·javascript·后端
Sendingab1 小时前
3.5 Spring Boot邮件服务:从基础发送到模板邮件进阶
spring boot·后端·python
uhakadotcom2 小时前
JDK 24新特性解读:提升性能、安全性和开发效率
后端·面试·github
运维小贺2 小时前
MySQL超详细介绍(近2万字)
运维·数据库·mysql
猫咪-95272 小时前
Mysql表的简单操作
服务器·数据库·mysql
小狗很可爱2 小时前
将Django连接到mysql
android·mysql·django