极高 进阶
count查询性能#
一句话答案#
InnoDB 下
count(*)和count(1)性能基本一致且优于count(字段),因为前两者由优化器选最小索引树遍历、不取值不判空,而count(字段)要取字段值并跳过 NULL;MyISAM 把总行数存在元数据里,无 where 时count(*)直接返回,所以极快。
核心要点
| 写法 | 语义 | InnoDB 行为 |
|---|---|---|
count(*) | 统计总行数(含 NULL 行) | 优化器专门优化,选最小的索引树遍历,不取值、不判空 |
count(1) | 统计总行数 | 与 count(*) 等价,server 层填常量 1,不判空 |
count(主键id) | 统计主键非 NULL 行数 | 遍历聚簇索引取 id 返回 server 层(id 非空必计),略慢 |
count(普通字段) | 统计该字段非 NULL 行数 | 取字段值 + 判断是否为 NULL,最慢 |
关键结论:
- 性能排序:
count(*) ≈ count(1) > count(主键) > count(普通字段) count(*)是 SQL 标准里专门定义的”统计行数”语法,不会真的展开所有列,无需担心列多变慢- 存储引擎差异:MyISAM 维护了精确总行数(无 where 时 O(1) 返回);InnoDB 因 MVCC 多版本,“当前事务能看到多少行”不固定,无法缓存,只能实时统计
面试回答(2分钟版)
先说结论:InnoDB 下
count(*)和count(1)性能几乎一样,都优于count(字段),所以统计行数就直接用count(*)。原理上,count(*)是 SQL 标准里专门的行数统计语法,优化器会选一棵最小的索引树(比如某个二级索引而不是聚簇索引)来遍历,对每一行直接累加、既不取列值也不判断 NULL;count(1)是 server 层对每行填一个常量 1,同样不判空,所以两者等价。而count(普通字段)必须把字段值读出来再判断是否为 NULL,非 NULL 才计入,多了取值和判空两步,最慢;count(主键)介于中间。为什么 InnoDB 统计这么慢?因为它支持 MVCC,同一时刻不同事务通过 Read View 能看到的行数不一样,没法像 MyISAM 那样把总行数缓存在表元数据里,只能一行行实时数。MyISAM 没有事务和 MVCC,所以维护了精确行数,
select count(*)不带 where 时直接返回,是 O(1)。优化方向上,如果是大表又要频繁取总数,一般不会硬数,而是维护一张计数表或用 Redis 计数器,写入时同步增减。
追问与易错
追问方向:
- “为什么 InnoDB 不能像 MyISAM 一样缓存行数?”→ 因为 MVCC,每个事务有自己的 Read View,同一行对不同事务可见性不同,“当前能看到的行数”是事务相关的,无法用一个全局值表示
- “count(*) 走哪个索引?”→ 优化器会挑一棵最小的、能覆盖统计的索引树(通常是占空间最小的二级索引),扫描成本最低;如果只有主键就走聚簇索引
- “千万级大表要实时显示总数怎么办?”→ 不要直接
count(*),常见方案:① 单独维护计数表,增删时事务内同步加减;② Redis 计数器(注意一致性);③ 对精度要求不高时用explain的 rows 估算值 - “count(*) 加了 where 还快吗?”→ MyISAM 带 where 也要实时扫,不再是 O(1);InnoDB 则尽量让 where 条件走索引,配合覆盖索引避免回表
易错点:
- ❌ 以为
count(*)会展开所有列导致变慢——它是专门语法,不取列值 - ❌ 以为
count(1)比count(*)快——InnoDB 里两者基本无差别,优化器同等对待 - ❌ 用
count(字段)当总行数——字段有 NULL 时会少数,语义就不是总行数了