计数的真实成本
统计行数看起来是最简单的操作,实现上却不便宜:存储引擎不维护一张表当前有多少行的精确计数,因此不带条件的计数必须扫描一份索引或数据来数。优化方向有两条:让扫描落在更小的索引上,或者干脆不实时数——把结果预先算好存成汇总表,或者用采样统计给出的近似值应付容量类展示。选择哪条路取决于这个数字要用来做什么。
基本信息
| 适用平台 | MySQL 8.0(InnoDB)/ PostgreSQL 12 及以上 |
|---|---|
| 引擎机制 | 不维护精确行数缓存,计数需要扫描 |
| 扫描选择 | 优化器会挑选代价较小的索引来数 |
| 带条件计数 | 条件列上有索引时可减少扫描量 |
| 近似来源 | 元数据里的估算值,几乎零成本 |
典型使用场景
- 分页总数:大表实时统计代价高,考虑缓存或游标分页
- 看板指标:按维度统计条数,适合用汇总表维护
- 检查数据是否到达:统计本次导入的行数与预期比对
- 容量评估:用近似值快速判断表规模
- 对账与计费:必须精确,用真实计数或专用汇总表
几种写法的成本
几种写法的成本
-- 带条件的精确计数:条件列有索引时可减少扫描量
SELECT COUNT(*) FROM orders WHERE user_id = 100;
-- 统计非空值:只数该列不为空的记录
SELECT COUNT(coupon_id) FROM orders WHERE user_id = 100;
-- 统计不同取值个数:需要去重,代价高于普通计数
SELECT COUNT(DISTINCT status) FROM orders WHERE user_id = 100;
-- MySQL 近似行数(估算值,仅用于趋势参考)
SELECT table_rows FROM information_schema.tables
WHERE table_schema = 'shop' AND table_name = 'orders';
-- PostgreSQL 近似行数(统计信息收集后更新)
SELECT reltuples::bigint AS estimate FROM pg_class WHERE relname = 'orders';
-- 高频精确计数的正解:维护计数表,把成本转移到写入侧写法与成本对照
| 写法 | 是否需要扫描 | 适用场景 |
|---|---|---|
| 无条件的计数 | 需要,选较小索引 | 表总体规模统计 |
| 带条件的计数 | 需要,走条件索引 | 按维度统计 |
| 统计指定列 | 需要,且不计空值 | 有非空语义的计数 |
| 去重计数 | 需要,代价更高 | 区分度分析 |
| 元数据估算值 | 不需要 | 容量预估与趋势 |
统计指定列与统计所有行不是一回事:前者只数该列非空的记录,在可空列上结果会偏小。需要一律统计行数时,用不带列名的形式或者数主键。
把精确计数改成维护计数表时,写入路径必须与业务在同一事务里更新,否则会出现长期漂移。上线后还要配一个定期校准作业,把计数表与真实计数对齐。