首页/数据/SQL 速查/COUNT 的精确成本与近似替代
性能与执行计划72 条

COUNT 的精确成本与近似替代

COUNT(*) 需要扫描符合条件的行:InnoDB 选择较小的索引扫描;海量数据考虑近似值与维护计数表。

SQL 速查数据更新于 2026-10-13

计数的真实成本

统计行数看起来是最简单的操作,实现上却不便宜:存储引擎不维护一张表当前有多少行的精确计数,因此不带条件的计数必须扫描一份索引或数据来数。优化方向有两条:让扫描落在更小的索引上,或者干脆不实时数——把结果预先算好存成汇总表,或者用采样统计给出的近似值应付容量类展示。选择哪条路取决于这个数字要用来做什么。

基本信息

适用平台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';

-- 高频精确计数的正解:维护计数表,把成本转移到写入侧

写法与成本对照

写法是否需要扫描适用场景
无条件的计数需要,选较小索引表总体规模统计
带条件的计数需要,走条件索引按维度统计
统计指定列需要,且不计空值有非空语义的计数
去重计数需要,代价更高区分度分析
元数据估算值不需要容量预估与趋势
统计指定列与统计所有行不是一回事:前者只数该列非空的记录,在可空列上结果会偏小。需要一律统计行数时,用不带列名的形式或者数主键。
把精确计数改成维护计数表时,写入路径必须与业务在同一事务里更新,否则会出现长期漂移。上线后还要配一个定期校准作业,把计数表与真实计数对齐。

易错点

  • 在高频接口里实时统计大表行数:每次请求都要扫描,数据库很快被打满
  • 把统计指定列当成统计行数:可空列上结果偏小,指标口径出现偏差
  • 拿元数据估算值做对账:估算值与真实行数可能差很多
  • 维护计数表时绕过写入口(批量导入、补数据),计数与实际长期不一致
  • 分页用精确总数做大表翻页:翻到后面几页代价剧增,应改用游标分页

数据来源与参考

  • MySQL 8.0 Reference Manual · Aggregate Function Descriptions
  • PostgreSQL Documentation · Aggregate Functions
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌