MySQL 中的 count 三兄弟:效率大比拼!🚀
一、快速结论(先看结论再看分析)
| 方式 | 作用 | 效率 | 一句话总结 |
|---|---|---|---|
count(*) |
统计所有行数 | ⭐⭐⭐⭐ 最高 | 我是专业的!我为统计而生 |
count(1) |
统计所有行数 | ⭐⭐⭐⭐ 同样高效 | 我是 count(*) 的马甲兄弟 |
count(列名) |
统计该列非 NULL 的行数 | ⭐⭐⭐ 较慢 | 我挑剔,我只数非空值 |
结论:用 count(*) 就对了! ✅
二、代码示例:亲测三兄弟的差别
准备测试数据
sql
-- 创建测试表
CREATE TABLE user_test (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50),
age INT,
email VARCHAR(100)
);
-- 插入测试数据(故意插入一些NULL值)
INSERT INTO user_test (name, age, email) VALUES
('张三', 25, 'zhangsan@example.com'),
('李四', NULL, NULL),
('王五', 30, 'wangwu@example.com'),
(NULL, 28, 'unknown@example.com'),
('赵六', 35, NULL);
测试查询
sql
-- 查看表中的数据
SELECT * FROM user_test;
/*
+----+--------+------+--------------------+
| id | name | age | email |
+----+--------+------+--------------------+
| 1 | 张三 | 25 | zhangsan@example...|
| 2 | 李四 | NULL | NULL |
| 3 | 王五 | 30 | wangwu@example.com |
| 4 | NULL | 28 | unknown@example.com|
| 5 | 赵六 | 35 | NULL |
+----+--------+------+--------------------+
*/
-- 测试1:count(*) 统计所有行数
SELECT count(*) FROM user_test; -- 结果:5 ✅
-- 翻译:"老板,我有多少行数据?全都要!"
-- 测试2:count(1) 统计所有行数
SELECT count(1) FROM user_test; -- 结果:5 ✅
-- 翻译:"老板,你给我个固定值1,我数有多少个1"
-- 测试3:count(列名) 统计非NULL的行数
SELECT count(name) FROM user_test; -- 结果:4 ❗(NULL的那行没算)
SELECT count(age) FROM user_test; -- 结果:4 ❗(NULL的那行没算)
SELECT count(email) FROM user_test; -- 结果:3 ❗(两个NULL都没算)
-- 翻译:"我只数有身份证的人,黑户不算"
三、深入剖析:它们到底有啥不同?
1. 语义区别(最重要的区别!)
sql
-- count(*) 是 SQL 标准写法
-- 意思:给我这个表有多少行数据
-- 相当于:"这个会议室有多少个座位?"
-- count(1) 是 count(*) 的一种写法
-- 意思:统计有多少个1
-- 相当于:"给每个座位发个苹果,最后数苹果"
-- count(列名) 是统计该列非NULL值的数量
-- 意思:这个会议室有多少人带了手机
-- 相当于:"检查每个座位,如果有人带了手机就计数"
2. 性能区别(神话与现实)
传说中的误解:
"count(1) 比 count(*) 快" ❌
"count(主键) 最快" ❌
现实真相:
MySQL 5.7 及以后版本:
count(*)和count(1)性能完全相同- MySQL 优化器会把它们当作一回事
查看执行计划证明:
sql
EXPLAIN SELECT count(*) FROM user_test;
EXPLAIN SELECT count(1) FROM user_test;
EXPLAIN SELECT count(id) FROM user_test;
-- 你会看到:前两个的执行计划完全一样!
性能排序(一般情况):
count(*)≈count(1)⭐⭐⭐⭐⭐count(主键列)⭐⭐⭐⭐count(非主键有索引列)⭐⭐⭐count(非主键无索引列)⭐⭐
为什么 count(列名) 可能更慢?
sql
-- 假设 email 列有索引
SELECT count(email) FROM user_test;
/*
MySQL 需要:
1. 读取索引(如果该列有索引)
2. 检查每个值是否为 NULL
3. 只计数非 NULL 的
如果 email 列没有索引:
1. 读取整行数据(比 count(*) 读的更多)
2. 检查 email 是否为 NULL
3. 只计数非 NULL 的
*/
3. 特殊情况分析
sql
-- 情况1:所有列都不允许NULL
CREATE TABLE user_not_null (
id INT PRIMARY KEY NOT NULL,
name VARCHAR(50) NOT NULL
);
-- 这时候:count(*) = count(id) = count(name)
-- 情况2:空表 vs NULL值
CREATE TABLE empty_table (id INT);
SELECT count(*) FROM empty_table; -- 结果:0
SELECT count(id) FROM empty_table; -- 结果:0
INSERT INTO empty_table VALUES (NULL);
SELECT count(*) FROM empty_table; -- 结果:1
SELECT count(id) FROM empty_table; -- 结果:0 ❗
四、实际工作中的选择指南
🎯 场景1:统计总行数
sql
-- ✅ 正确做法
SELECT count(*) FROM orders;
-- ❌ 错误做法
SELECT count(order_id) FROM orders; -- 万一有NULL呢?
SELECT count(1) FROM orders; -- 能用,但不是标准
🎯 场景2:统计有效数据数量
sql
-- 统计有多少用户填写了邮箱
SELECT count(email) FROM users; -- ✅ 这个场景就该用 count(列名)
-- 统计已完成订单数量(假设 status=2 是已完成)
SELECT count(*) FROM orders WHERE status = 2; -- ✅
🎯 场景3:统计非重复值
sql
-- 统计有多少个不同的城市
SELECT count(DISTINCT city) FROM users; -- ✅ count + DISTINCT
-- 统计有多少个城市,排除 NULL
SELECT count(DISTINCT city) FROM users; -- DISTINCT 会自动排除 NULL
五、性能优化技巧
1. 大表优化方案
sql
-- 方案1:使用近似值(适用于统计概览)
SELECT TABLE_ROWS
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = 'your_db' AND TABLE_NAME = 'big_table';
-- 方案2:分页总数缓存(适用于列表页)
-- 第一次查询时缓存总数,后面定时更新
-- 方案3:使用汇总表
CREATE TABLE stats_daily (
date DATE PRIMARY KEY,
user_count INT,
order_count INT
);
2. 索引优化
sql
-- 为 count(列名) 创建索引
CREATE INDEX idx_email ON users(email);
-- 但注意:count(*) 不一定需要索引,InnoDB有优化
六、有趣比喻帮你记忆
🍔 汉堡店排队比喻
sql
-- 有10个人在排队买汉堡
count(*) = "队列里有10个人" ✅
count(1) = "我给每人发个号码牌,数有10个牌" ✅
count(现金) = "只有8个人带了现金" ❗
count(会员卡) = "只有5个人有会员卡" ❗
🏫 教室点名比喻
sql
-- 教室里有50个座位
count(*) = "教室有50个座位" ✅
count(1) = "我在每个座位放本书,最后数有50本" ✅
count(学生) = "今天来了45个学生上课" ❗(空座位不算)
七、总结与最佳实践
📋 最终建议
-
统计总行数,一律用
count(*)- 这是 SQL 标准写法
- 性能最优(MySQL有专门优化)
- 语义最明确
-
统计某列非 NULL 数量,用
count(列名)- 这是它的本职工作
- 不要用它统计总行数
-
关于
count(1)- 性能与
count(*)一样 - 但不够"标准",像方言
- 建议统一用
count(*)
- 性能与
-
性能关键点
- 大表避免频繁 count
- 考虑使用缓存或汇总表
- 为 count(列名) 的列加索引
📊 一张图看懂
count(*) -> 总数 -> 最快 -> 推荐使用
↓
count(1) -> 总数 -> 一样快 -> 可用但不标准
↓
count(主键) -> 总数 -> 次快 -> 主键非NULL时可用
↓
count(索引列) -> 非NULL数 -> 较慢 -> 有索引时可用
↓
count(普通列) -> 非NULL数 -> 最慢 -> 谨慎使用
记住口诀:"数总数,用星号;数非空,列名好;数字1,虽高效,不是标准别当宝" 🎯