- 后端
- 数据库
- 文档数据库
【免费下载链接】FerretDB
A truly Open Source MongoDB alternative
聚合操作(Aggregation Operations)是文档数据库中对大批量数据记录执行分组、排序、重组或修改等处理的核心能力。在 FerretDB(v1.24 文档版)中,这些操作通过由多个阶段(Stage)串接而成的管道(Pipeline)完成,并由顶层命令aggregate统一驱动。本文以 version-v1.24 的聚合操作文档 为骨架,结合仓库中 命令注册表、aggregate 命令处理实现 与 集成测试 源码,完整讲解聚合管道的运行原理、aggregate命令的用法、全部受支持阶段,以及它在 FerretDB 内部的真实调用链,让读者既能直接上手写出可运行的聚合管道,也能理解其背后的执行机制。
什么是聚合管道:阶段串接的数据流水线
聚合操作用于对大量数据记录执行各种处理,例如数据分组(grouping)、排序(sorting)、重组(restructuring)或修改(modifying)。这些操作会依次经过一个或多个阶段,这些阶段共同构成一条管道(pipeline)。
管道中每个阶段都作用于前一阶段返回的文档,起始输入为集合中的原始文档。如下图所示,文档按顺序流经管道,前一阶段的结果作为下一阶段的输入,例如$match→$group→$sort的典型链路:
理解这一模型的关键在于"数据流"视角:管道不是一次性执行整个复杂查询,而是把复杂查询拆解为相互独立的阶段,记录逐级经过一系列变换,直到最终产出期望结果。每个阶段只对上游传入的文档集执行一种职责(过滤、分组、排序、投影等),这种设计让聚合逻辑清晰、可组合、可测试。
一个完整的管道示例:按品类汇总销售额
先准备测试数据。向sales集合中插入如下 8 条销售记录:
db.sales.insertMany([ { _id: 1, category: 'Electronics', price: 1000 }, { _id: 2, category: 'Electronics', price: 800 }, { _id: 3, category: 'Clothing', price: 30 }, { _id: 4, category: 'Clothing', price: 50 }, { _id: 5, category: 'Home', price: 1500 }, { _id: 6, category: 'Home', price: 1200 }, { _id: 7, category: 'Books', price: 20 }, { _id: 8, category: 'Books', price: 40 } ])一个典型的聚合管道如下:
db.sales.aggregate([ { $match: { category: { $ne: 'Electronics' } } }, { $group: { _id: '$category', totalPrice: { $sum: '$price' }, productCount: { $sum: 1 } } }, { $sort: { totalPrice: -1 } } ])逐步拆解这段管道:
$match阶段:先过滤掉category字段为Electronics的所有文档,只保留其他品类。它等价于一次find查询操作。$group阶段:按category字段对剩余文档分组,并为每个品类计算totalPrice(对price字段求和)和productCount(每文档计数为 1 再求和)。$sort阶段:按totalPrice字段降序排序,让销售额最高的品类排在最前面。
上述管道最终返回如下结果:
[ { _id: 'Home', totalPrice: 2700, productCount: 2 }, { _id: 'Clothing', totalPrice: 80, productCount: 2 }, { _id: 'Books', totalPrice: 60, productCount: 2 } ]注意结果中Electronics已被$match过滤掉,其余三个品类按总价从高到低排列,且每个品类都带上了商品件数——一次复杂的"过滤 + 分组聚合 + 排序"需求,被拆成了三个清晰、可单独验证的阶段。
aggregate 命令:驱动管道执行的顶层命令
aggregate是用于跨多个管道阶段聚合数据的顶层命令(top-level command)。它作用于某个集合,允许你在由"一个或多个阶段 + 运算符"组成的管道中指定聚合操作,用于对数据进行分组、过滤、排序、投影和聚合计算等变换与分析。
其基础用法与上面的示例一脉相承:
// Aggregation pipeline to perform aggregation operations on a collection db.collection.aggregate([ // Stage 1: Matching documents based on a specific field and value { $match: { field: value } }, // Stage 2: Grouping documents by the "category" field and calculating the sum of the "quantity" field { $group: { _id: '$category', total: { $sum: '$quantity' } } } ])从源码角度,aggregate在 FerretDB 中被注册为受支持命令。在 internal/handler/commands.go 的命令注册表中可以看到:
"aggregate": { handler: h.msgAggregate, Help: "Returns aggregated data.", },其处理函数实现在 internal/handler/msg_aggregate.go 中,核心调用链如下:
- 通过
h.s.CreateOrUpdateByLSID处理逻辑会话(LSID),保证聚合操作在会话上下文内执行; - 通过
getRequiredParamstring读取请求中的数据库名; - 调用
h.p.Aggregate(connCtx, dbName, req.DocumentRaw())将整个聚合请求交给后端执行层处理; - 执行成功后,
h.s.AddCursor将返回的游标 ID 注册到会话中(对应 internal/documentdb/pool_cursors.go 中func (p *Pool) Aggregate(...)返回的分页结果与游标 ID),最后由middleware.ResponseDoc封装为响应返回给客户端。
从这条调用链可以看到,FerretDB 的aggregate不仅完成管道计算,还自动参与游标(cursor)与分页(batch)机制:当聚合结果集较大时,首屏数据与游标 ID 一起返回,客户端可通过getMore命令继续拉取后续批次(对应仓库中的 getMore 实现)。
受支持的聚合阶段一览
管道由若干聚合阶段组成,每个阶段都作用于前一阶段的输出结果,从输入文档开始逐级处理。FerretDB v1.24 文档所定义的受支持聚合阶段及其说明如下表所示(来源:version-v1.24 聚合阶段文档):
| 支持的聚合阶段 | 说明 |
|---|---|
$count | 返回指定查询中所有匹配文档的数量 |
$group | 基于特定值或表达式对文档分组,并为每个分组返回单个文档 |
$limit | 限制特定数量的文档,将其余文档传给下一阶段 |
$match | 相当于一次find操作,只把匹配指定查询的文档传给下一阶段 |
$project | 指定文档中要传给管道下一阶段的字段 |
$skip | 跳过指定数量n的文档,将其余文档传给下一阶段 |
$sort | 基于指定顺序对全部文档进行排序并返回 |
$unset | 指定要从文档中移除/排除的字段 |
$unwind | 解构数组字段,为数组中的每个元素返回一个文档 |
这九个阶段覆盖了聚合最常用的能力:过滤($match)、分组聚合($group)、排序($sort)、裁剪($limit/$skip)、投影($project/$unset)、计数($count)与数组展开($unwind)。它们既可以单独使用,也可以任意组合成多阶段管道。
这些阶段的正确性与行为在仓库的兼容性测试中得到系统验证。例如 integration/aggregate_compat_test.go 中包含大量针对各阶段的对照测试用例:
TestAggregateCompatMatch(aggregate_compat_test.go):验证$match阶段的过滤语义;TestAggregateCompatGroup(aggregate_compat_test.go):验证$group的分组与聚合表达式(如$sum)行为;TestAggregateCompatSort(aggregate_compat_test.go):验证$sort的排序规则;TestAggregateCompatUnwind(aggregate_compat_test.go):验证$unwind对数组字段的解构展开;TestAggregateCompatSkip/TestAggregateCompatLimit/TestAggregateCompatProject/TestAggregateCompatUnset等分别覆盖$skip、$limit、$project、$unset;TestAggregateCompatEmptyPipeline(aggregate_compat_test.go):验证空管道时直接返回全部文档(等价于无阶段处理的find);TestAggregateCompatCount(aggregate_compat_test.go):验证$count阶段的计数行为。
此外,错误路径也有保障:integration/aggregate_test.go 中的TestAggregateAddFieldsErrors等用例断言了非法管道输入(如阶段规范不是对象、字段路径以$开头)时的报错码与报错信息,例如$addFields specification stage must be an object与错误码40272。
实战建议与注意事项
- 阶段顺序影响性能与结果:尽量把
$match、$limit、$skip等裁剪性阶段前置,让后续$group、$sort只处理必要数据;同时注意不同阶段顺序(例如先$limit再$sort与先$sort再$limit)会产生不同的语义结果。 $group的_id必填:分组阶段必须以_id指定分组依据(可为字段路径如'$category',也可为常量表达式);其余输出字段通过$sum、$count等累加/聚合表达式计算。- 字段路径使用
$前缀:在$group、$sort、$project等阶段中引用文档字段时使用$field形式,避免与普通字符串值混淆。 - 结果集较大时依赖游标分页:如前文源码分析所示,
aggregate返回首屏数据与游标 ID,后续通过getMore拉取剩余批次,无需一次性在内存中加载全量结果。
总结
聚合管道把复杂的数据处理拆解为$match→$group→$sort等相互独立的阶段,每一阶段消费上一阶段的输出并产出下一阶段的输入;aggregate作为顶层命令负责承载管道定义并驱动执行。本文既给出了可直接运行的示例管道与输出,也结合 commands.go 的命令注册、msg_aggregate.go 的会话与游标处理,以及 aggregate_compat_test.go 的兼容性测试,完整还原了 FerretDB 中聚合功能的"文档用法 → 命令注册 → 后端执行 → 游标返回 → 测试验证"全链路。读者可以继续深入阅读 聚合阶段文档 与 聚合操作文档目录,并结合 version-v1.24 聚合相关用法文档 进一步探索运算符的细节。
- 后端
- 数据库
- 文档数据库
【免费下载链接】FerretDB
A truly Open Source MongoDB alternative
相关推荐
InsForge 性能实测:数据库、冷启动与 AI 网关三项数据,选型前看这篇
InsForge 性能实测:数据库、冷启动与 AI 网关三项数据,选型前看这篇 InsForge 是一个开源一站式后端平台,为编程代理提供 Postgres 数
后端数据库文档数据库FerretDB v1.4.0 新特性详解:唯一索引与聚合管道 `$type`、`$set`、`$addFields`、`$unset` 实战
FerretDB v1.4.0 新特性详解:唯一索引与聚合管道 $type 、 $set 、 $addFields 、 $unset 实战 本篇技术指南以 Fe
后端数据库文档数据库UVR5 完整教程:免费人声分离,三步提取第一版伴奏
UVR5 完整教程:免费人声分离,三步提取第一版伴奏 想翻唱一首歌,却怎么也找不到原版伴奏?UVR5(Ultimate Vocal Remover)是一个免费开
后端数据库文档数据库
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考