你是否在写SQL时纠结过:
count(*)、count(1)和count(字段)到底该用哪个?它们性能有区别吗?为什么有的统计结果不一样?今天我将彻底解开这些谜团,让你的SQL查询更专业高效!
一、初识COUNT:数据统计的"瑞士军刀"🔧
COUNT的基本作用
COUNT()是MySQL中最常用的聚合函数,用于统计行数或非空值的数量。就像超市收银员统计顾客数量:
count(*):统计所有经过收银台的顾客count(1):统计所有经过收银台的顾客(另一种方式)count(会员卡):只统计有会员卡的顾客
二、深入对比:三种COUNT方式详解🔬
核心区别对比表
| 特性 | count(*) | count(1) | count(字段) |
|---|---|---|---|
| 统计对象 | 所有行 | 所有行 | 指定字段非空行 |
| NULL处理 | 包含NULL行 | 包含NULL行 | 排除NULL行 |
| 性能比较 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐ |
| 索引利用 | 最优 | 良好 | 依赖字段索引 |
| 使用场景 | 统计总行数 | 统计总行数 | 统计非空值 |
| 结果差异 | 可能不同 | 可能不同 | 可能不同 |
1. count(*) - 全能选手🏆
作用: 统计表中的总行数(包括NULL值所在行)
-- 创建示例表
CREATE TABLE users (
id INT PRIMARY KEY,
name VARCHAR(50),
phone VARCHAR(15) -- 允许NULL
);
INSERT INTO users VALUES
(1, '张三', '13800138000'),
(2, '李四', NULL),
(3, '王五', '13900139000');
-- 使用count(*)
SELECT count(*) FROM users; -- 结果:3
特点:
- 最直接的"行数统计"方式
- MySQL 5.7+版本做了特殊优化
- 推荐作为行数统计的首选方式
2. count(1) - 神秘数字侠🔢
作用: 统计表中的总行数(包括NULL值所在行)
-- 使用count(1)
SELECT count(1) FROM users; -- 结果:3
特点:
1不是指第一列,而是表示常量值- 性能与
count(*)几乎相同 - 早期版本可能稍慢于
count(*),现代版本无差别
3. count(字段) - 精准狙击手🎯
作用: 统计指定字段的非空值数量
-- 使用count(字段)
SELECT count(phone) FROM users; -- 结果:2(排除NULL值)
特点:
- 只统计非NULL值
- 性能取决于字段是否有索引
- 结果可能小于总行数
三、执行原理:MySQL如何统计?⚙️
执行流程对比
索引的影响
当使用count(字段)时:

性能对比测试:
-- 创建百万数据表
CREATE TABLE big_data (
id INT PRIMARY KEY AUTO_INCREMENT,
data VARCHAR(100),
indexed_col INT,
INDEX (indexed_col)
);
-- 插入100万条随机数据
INSERT INTO big_data (data, indexed_col)
SELECT
UUID(),
FLOOR(RAND() * 1000)
FROM
(SELECT 0 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3) a,
(SELECT 0 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3) b,
... -- 此处省略生成代码
-- 性能测试
SELECT BENCHMARK(1000, count(*)) FROM big_data; -- 平均0.05秒
SELECT BENCHMARK(1000, count(1)) FROM big_data; -- 平均0.05秒
SELECT BENCHMARK(1000, count(id)) FROM big_data; -- 平均0.05秒(主键索引)
SELECT BENCHMARK(1000, count(data)) FROM big_data; -- 平均1.8秒(无索引)
SELECT BENCHMARK(1000, count(indexed_col)) FROM big_data; -- 平均0.06秒(二级索引)
四、经典误区:90%程序员踩过的坑🕳️
误区1:count(1)比count(*)快
真相: 现代MySQL版本中两者性能相同,甚至count(*)可能更优!
官方文档说明:
InnoDB handles SELECT COUNT(*) and SELECT COUNT(1) operations in the same way. There is no performance difference.
误区2:count(主键)最快
真相: count(*)会优先使用占用空间最小的索引(通常不是主键)
EXPLAIN SELECT count(*) FROM big_data;
-- 可能使用二级索引而非主键索引
误区3:count(字段)可以统计所有行
真相: 当字段包含NULL值时,统计结果会小于实际行数
-- 错误示例
SELECT count(email) FROM users; -- 结果可能小于实际用户数
-- 正确做法
SELECT count(*) FROM users WHERE email IS NOT NULL; -- 明确过滤条件
五、性能优化:让COUNT飞起来🚀
1. 超大表的COUNT优化
当表数据量超过千万时:
| 方案 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| 近似值 | 使用SHOW TABLE STATUS | 极快 | 不精确 |
| 缓存计数 | 额外表存储计数 | 实时 | 维护成本高 |
| 分页累计 | 分段统计后求和 | 不锁表 | 复杂 |
示例:使用近似值
SHOW TABLE STATUS LIKE 'big_data';
-- 查看Rows字段(约99.9%准确)
示例:缓存计数表
-- 创建计数表
CREATE TABLE table_counts (
table_name VARCHAR(100) PRIMARY KEY,
row_count BIGINT
);
-- 更新机制(使用触发器或应用层维护)
2. 索引优化技巧
-- 创建更小的索引
ALTER TABLE big_data ADD INDEX idx_tiny (tiny_col);
-- 使用覆盖索引
SELECT count(indexed_col) FROM big_data; -- 直接使用索引数据
3. 架构优化
六、实战场景选择指南🧭
场景1:统计总用户数
-- ✅ 正确做法
SELECT count(*) FROM users;
-- ❌ 错误做法
SELECT count(id) FROM users; -- 多余的主键查找
场景2:统计有手机号的用户
-- ✅ 正确做法
SELECT count(phone) FROM users; -- 明确统计非空值
-- 等价写法
SELECT count(*) FROM users WHERE phone IS NOT NULL;
场景3:统计不同状态订单数
-- 使用条件聚合
SELECT
count(*) AS total_orders,
count(CASE WHEN status='PAID' THEN 1 END) AS paid_orders,
count(CASE WHEN status='SHIPPED' THEN 1 END) AS shipped_orders
FROM orders;
场景4:多表关联统计
-- 统计每个用户的订单数(包含无订单用户)
SELECT
u.id,
u.name,
count(o.id) AS order_count -- 注意用count(o.id)而非count(*)
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id;
七、终极总结:COUNT使用黄金法则🌟
1. 选择原则

2. 性能排序(从优到劣)

3. 最佳实践清单
- ✅ 首选
count(*)用于行数统计 - ✅ 明确业务需求:要总行数还是非空值?
- ✅ 为统计字段加索引 优化
count(字段) - ✅ 避免
count(非索引大字段)如TEXT/BLOB - ✅ 利用近似值 优化海量数据统计
- ❌ 不要用
count(列)代替count(*) - ❌ 不要假设主键统计最快
- ❌ 不要忽视NULL值的影响
4. 三种COUNT终极对比
| 维度 | count(*) | count(1) | count(字段) |
|---|---|---|---|
| 语义 | 统计所有行 | 统计所有行 | 统计非空值 |
| NULL处理 | 包含 | 包含 | 排除 |
| 性能 | 最优 | 等同count(*) | 依赖索引 |
| 索引利用 | 使用最小索引 | 使用最小索引 | 仅用该字段索引 |
| 结果准确性 | 总行数 | 总行数 | 非空值数量 |
| 推荐指数 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
八、进阶挑战:COUNT的冷知识❄️
1. MyISAM表的魔法
对于MyISAM存储引擎:
SELECT count(*) FROM myisam_table; -- 直接返回元数据,O(1)时间复杂度
2. 事务隔离的影响
在可重复读隔离级别:
-- 会话1
START TRANSACTION;
SELECT count(*) FROM users; -- 返回100
-- 会话2
INSERT INTO users(name) VALUES('新用户');
-- 会话1再次查询
SELECT count(*) FROM users; -- 仍返回100(MVCC特性)
3. COUNT与DISTINCT组合
-- 统计不同城市用户数
SELECT count(DISTINCT city) FROM users;
-- 执行计划:需要临时表去重
终极忠告:
- 日常开发首选
count(*)- 统计非空值用
count(字段)- 忘记那些过时的性能传说
- 理解业务需求是核心!
思考题: 在分布式数据库(如分库分表)中,如何高效实现count(*)?欢迎在评论区分享你的方案!
(本文基于MySQL 8.0,不同版本实现细节可能略有差异)

4万+

被折叠的 条评论
为什么被折叠?



