大表计数:精确与近似
在 InnoDB 里统计行数不是读一个缓存变量,而是真的要遍历符合条件的记录,所以大表上的全量计数会成为明显的慢查询。数据库另外维护了一份抽样统计信息,可以快速给出一个近似行数,代价几乎为零,但准确度取决于采样与数据变化。工程上的取舍很清楚:容量预估、趋势展示用近似值;对账、计费、精确分页总数这类场景必须用真实计数,或者提前把结果维护成汇总表。
基本信息
| 适用平台 | MySQL 8.0(InnoDB)/ PostgreSQL 12 及以上 |
|---|---|
| MySQL 近似来源 | 元数据表里的估算行数列,来自采样统计 |
| PostgreSQL 近似来源 | 系统表里的元组估算数,随统计信息收集更新 |
| 精确计数成本 | 需要扫描符合条件的数据或较小索引 |
| 常用替代 | 维护计数表、按维度增量汇总、把总数缓存到应用层 |
典型使用场景
- 容量评估:快速看某张表大概多少行,判断是否需要分表
- 后台展示「约多少条」之类的提示,允许存在偏差
- 监控阈值判断:行数超过某个量级时触发人工关注
- 报表趋势对比:只看数量级变化,不在意个位差
- 对账与计费:必须精确,改用真实计数或专用汇总表
- 分页总数:大表上实时算总数代价高,可缓存或改用游标分页
近似与精确的写法
近似与精确的写法
-- MySQL:采样统计给出的估算行数,误差可能较大
SELECT table_name, table_rows
FROM information_schema.tables
WHERE table_schema = 'app' AND table_name = 'orders';
-- PostgreSQL:随统计信息收集更新的元组估算数
SELECT reltuples::bigint AS approx_rows
FROM pg_class WHERE relname = 'orders';
-- 精确计数:条件列上有索引时可减少扫描量
SELECT COUNT(*) FROM orders WHERE user_id = 100;
-- 精确计数前的准备:统计信息过期会让估算偏差更大
ANALYZE TABLE orders;两种口径对照
| 口径 | 来源 | 更新时机 | 适用场景 |
|---|---|---|---|
| 估算行数(MySQL) | 元数据表里的统计值 | 由统计信息采样更新 | 容量预估、趋势展示 |
| 估算行数(PostgreSQL) | 系统表元组估算 | 统计信息收集后更新 | 计划估算、容量预估 |
| 精确计数 | 实际扫描数据或索引 | 每次查询实时计算 | 对账、计费、精确总数 |
| 计数表 | 业务写入时同步维护 | 与业务事务一起提交 | 高频精确计数的接口 |
估算行数与真实行数可能存在明显偏差,数据变化剧烈时偏差更大。任何与金额、对账、计费相关的计数都不能用估算值。
维护计数表要在同一个事务里随业务写入一起更新,否则会出现长期漂移;上线后还要有定期校准的作业,把计数表与真实计数对齐。