预计算字段需绑定业务节奏与一致性要求,适用于$group瓶颈、高频读低频写、维度固定及允许最终一致的场景;应嵌入主文档用原子操作维护,避免更新链断裂。

预计算文档不是“加个字段就行”,而是要和业务更新节奏、查询频率、数据一致性要求强绑定。直接在聚合时实时算,性能扛不住;全靠应用层缓存,又容易过期或不一致。最稳的路是把统计逻辑下沉到写入路径里。
什么时候该用预计算字段代替 $group
当出现以下任一情况时,$group 聚合已成瓶颈:单次查询响应超 500ms、聚合管道中 $match 后仍需扫描 >10 万文档、同一统计口径被高频(>10 次/分钟)并发请求、统计结果用于前端实时展示(如销售看板)。
- 高频读 + 低频写:比如用户等级积分榜,每天只增删几次用户,但每秒被查上百次
- 统计维度固定:如“按 category 统计销量”“按 date 统计日活”,不是任意字段组合
- 允许最终一致性:比如销售总额允许 1–2 秒延迟,不强制事务级精确
预计算字段怎么嵌进原始文档里
别新建集合存汇总数据,直接在业务主文档里加统计字段。例如订单集合 orders 中每条记录代表一笔交易,就顺便维护所属商品的累计销量:
db.orders.insertOne({
_id: ObjectId("..."),
item_id: "SKU-123",
price: 299,
quantity: 2,
// 预计算字段:这个 item_id 当前总销量(由写入逻辑维护)
item_total_sales: 1427,
// 可选:带时间戳的快照,方便核对
item_total_sales_updated_at: ISODate("2026-07-21T17:45:32Z")
})
- 写入新订单时,用
updateOne带$inc更新对应items文档的total_sales字段 - 删除/退货时,同样用
$inc减去对应数量,而不是重跑聚合 - 避免在应用层做“先查再改”,必须用原子操作(
findAndModify或带条件的updateOne)防止并发覆盖
聚合管道里怎么安全复用预计算字段
预计算字段不是取代聚合,而是让它变轻。比如查“Top 10 热销品类”,不再用 $group 扫全量订单,而是:
db.items.aggregate([
{ $match: { status: "active" } },
{ $sort: { total_sales: -1 } },
{ $limit: 10 }
])
- 前提是
items集合里每个文档都存了total_sales,且它由订单写入流程实时更新 - 如果还需“近 7 天销量”,就不能只靠这个字段——得另建一个
sales_7d字段,配合 TTL 索引或定时 job 清理 - 注意:预计算字段不能用于需要精确去重的场景(如 UV),因为
$sum是累加,不是$addToSet后计数
最容易被忽略的坑:更新链断裂
预计算字段的生命线是写入逻辑的健壮性。一旦漏掉某类写操作(比如后台导入订单、第三方 webhook 回调、数据库直连修复),统计就失真,而且很难自动发现。
- 必须给预计算字段加
ttl索引或定期校验 job(比如每天凌晨用聚合结果比对一次) - 所有写入口(API、后台任务、ETL 脚本)都要走同一套更新逻辑,不能有的走聚合、有的走字段自增
- 不要用
$set直接赋值,一律用$inc或$push+$size这类原子操作,避免竞态
预计算不是银弹,它把计算压力从查询时挪到了写入时,也把数据一致性责任从数据库交到了业务代码手上。写错一次,可能比慢十次查询更难排查。


















