服务端与数据 / 进阶
MongoDB 聚合与建模:先按读取路径决定嵌入还是引用
MongoDB 的灵活结构不等于无需建模。本文以订单统计为例,说明嵌入与引用、聚合管道顺序和 explain 分析。
一起读取、一起更新的数据优先嵌入
订单行项目通常随订单一起读取,数量也有明确上限,适合嵌入。用户的全部历史订单会无限增长,不应塞进用户文档。引用关系适合独立生命周期、高基数或需要单独查询的实体。
嵌入:读取原子、减少关联,但要控制数组和文档大小;引用:实体独立、增长安全,但读取时可能需要额外查询或 $lookup;快照:订单中保存购买时的商品名和价格,避免商品更新改写历史。
尽早过滤,尽晚展开
把能命中索引的 $match 放在管道前面,减少后续阶段的数据量。$project 可以尽早去掉大字段;$unwind 和 $lookup 可能放大记录数,应在过滤之后使用。
EXAMPLE / 02MongoDB
db.orders.aggregate([
{ $match: {
tenantId: 't_42',
status: 'paid',
createdAt: { $gte: ISODate('2026-03-01') }
}},
{ $group: {
_id: { $dateTrunc: { date: '$createdAt', unit: 'day' } },
revenue: { $sum: '$total' },
orderCount: { $sum: 1 }
}},
{ $sort: { _id: 1 } }
]);用 explain 检查是否真的走索引
关注 totalDocsExamined 与 nReturned 的差距、是否出现 COLLSCAN、排序是否溢出内存。报表聚合频繁且数据量大时,考虑增量维护汇总集合,而不是每次扫描全部原始订单。
MongoDB 单文档有 16MB 上限。无限增长数组还会造成更新成本和热点文档问题,应拆成独立集合。
EXAMPLE / 03MongoDB
db.orders.createIndex({
tenantId: 1,
status: 1,
createdAt: 1
});
db.orders.explain('executionStats').aggregate(pipeline);