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

前言

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


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

测试环境:MySQL 8.0.40,InnoDB 引擎
提示:使用 EXPLAIN SELECT COUNT(...) 可以查看优化器选择的执行计划。

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

CREATE TABLE user (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(50),
    email VARCHAR(100) 
);

-- 插入测试数据
INSERT INTO user (name, email) VALUES
('张三', 'zhangsan@example.com'),
('李四', NULL),
('王五', 'wangwu@example.com'),
('赵六', NULL);

执行以下查询:

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(主键) 是最优选择。但实际情况如何?我们通过实验验证。

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

CREATE TABLE big_table (
    id INT PRIMARY KEY AUTO_INCREMENT,
    data VARCHAR(255),
    index idx_data (data)
);

随机生成100w数据
在这里插入图片描述

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

SELECT COUNT(*) FROM big_table;

SELECT COUNT(1) FROM big_table;

执行查询结果,如下图:
在这里插入图片描述

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

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

-- 使用主键索引
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:统计所有行数(如分页总条数)

-- ✅ 推荐写法
SELECT COUNT(*) FROM user;

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

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

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

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

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

四、常见误区与避坑指南

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

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

-- 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 分析执行计划才是王道。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

四七伵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值