count(*)、count(1)、count(字段) 到底有什么区别?
三者统计逻辑不同,但
count(*)和
count(1)在 InnoDB 下行为完全一致,都是统计满足 WHERE 条件的所有行(含全 NULL 行);而
count(字段)会跳过该字段值为
NULL的行。
count(*):语义最清晰,MySQL 优化器会自动选择成本最低的索引(如主键)遍历计数,不依赖字段是否可空
count(1):本质是常量表达式,和
count(*)执行计划、性能无差别,纯属写法偏好
count(age):只统计
age IS NOT NULL的行,若该字段大量为 NULL,结果会明显小于
count(*)
为什么加了索引,count(*) 还很慢?
因为 InnoDB 引擎下,
count(*)不像 MyISAM 那样直接读取表元数据中的“行数缓存”,而是必须扫描索引树——哪怕只是扫主键索引,百万级数据仍需 IO 和遍历开销。 有主键时,MySQL 优先用主键索引(
type=index),比全表扫描快,但仍是 O(n) 没有主键、只有普通索引时,可能选最小的非空索引,但若索引列允许 NULL,部分版本可能退化为全表扫描 千万级大表慎用
count(*)实时查总数,考虑用缓存或异步更新的统计表替代
统计去重数或带条件的行数,怎么写才不出错?
别硬套
count()去实现条件计数,容易漏 NULL 或逻辑翻车。 要统计「年龄大于 25 的人数」:直接用
count(*)+
WHERE age > 25,别写
count(IF(age > 25, 1, NULL))—— 多余且易错 要统计「不同部门数量」:用
count(DISTINCT department),注意该写法自动忽略
department IS NULL的行 要统计「有邮箱且邮箱不重复的人数」:
count(DISTINCT c_email)即可,无需额外
WHERE c_email IS NOT NULL(
DISTINCT本身已跳过 NULL)
GROUP BY 配合 count() 时最容易踩的坑
常见错误是误以为
count(*)会按分组“过滤掉 NULL”,其实它统计的是每组内所有行,包括该组中某列为 NULL 的记录。 比如
SELECT gender, count(*) FROM users GROUP BY gender,如果
gender有 NULL 值,就会单独产生一行
NULL | 12想排除 NULL 分组?得显式写
WHERE gender IS NOT NULL想统计每组里「name 不为空的人数」?用
count(name),不是
count(*)—— 后者统计整组行数,前者只算
name非空的行 实际开发中,最常被忽略的是:InnoDB 的
count(*)没有“瞬时快照”属性,它反映的是当前事务隔离级别下可见的行数,RR 级别下可能因 MVCC 读到旧版本导致计数偏少——这不是 bug,是设计使然。
