MongoDB聚合管道中的$version操作符是4.4版本引入的表达式操作符,它返回执行当前聚合操作的那一个mongod实例的MongoDB服务器版本字符串。这个操作符与常见的$literal、$concat等不同,它没有任何输入参数,也不能接收文档字段引用,它的作用就是把版本信息变成一条普通字段,方便后续阶段或者最终结果携带环境信息。需要注意的是,这里返回的是单个mongod实例的版本号,而不是mongos、配置服务器或者整个集群的版本。

从语法上看,$version必须写成{ $version: {} }这种空文档形式。虽然它看起来像一个操作符带了一个空对象参数,但这里的空文档并不是真正的过滤条件或表达式,只是MongoDB聚合表达式语法要求操作符后面必须跟一个值,因此空文档就是固定的调用方式。如果写成{ $version: 1 }或者{ $version: true },都会被解析为非法参数而报错。下面先结合示例说明它的基本行为。
一、$version操作符的语法与返回结果
在聚合管道中,$version属于表达式操作符,可以用在任何支持表达式的阶段里,例如$project、$addFields、$set以及$replaceWith等。它的调用格式固定为{ $version: {} },返回结果是一个字符串,例如4.4.6、5.0.14或7.0.2,不会携带build信息、Git版本或操作系统等额外数据。
下面是一个最基本的用法,在$project阶段把版本号输出到新字段serverVersion中:
db.products.aggregate([
{
$project: {
productName: 1,
serverVersion: { $version: {} }
}
}
])
这段管道从products集合读取文档,只保留productName字段,同时为每条输出文档添加一个serverVersion字段。由于$version不依赖任何输入文档字段,所有输出文档的版本号都相同。如果在一个MongoDB 5.0.14实例上执行,返回结果可能类似{ productName: '键盘', serverVersion: '5.0.14' }。
需要特别区分的是,$version与系统变量$$NOW、$$CLUSTER_TIME的命名规则不同。系统变量以$$开头,而$version是表达式操作符,以单个美元符号开头。部分开发者会误写成$$version,这样的写法在MongoDB中并不会被识别为任何内置变量,通常会直接报错。
二、在$project与$addFields阶段中的实际用法
实际开发中,$addFields比$project更适合追加版本信息,因为它不会删除原文档的其他字段。$project默认只输出显式指定的字段,如果只想增加一个字段而不改变原文档结构,使用$addFields会更安全。
例如从订单集合中读取文档,同时给每条记录加上mongoVersion字段,可以这样写:
db.orders.aggregate([
{
$addFields: {
mongoVersion: { $version: {} }
}
},
{
$project: {
orderId: 1,
amount: 1,
mongoVersion: 1
}
}
])
这段管道先在$addFields阶段给每个输入文档添加mongoVersion字段,随后在$project阶段只输出订单编号、金额和版本号。如果原订单文档中还包含客户信息、地址、状态等字段,它们不会被带到最终输出中。若希望保留所有原字段,只去掉_id,可以省略后续的$project,然后在$addFields之后再加一个$unset阶段即可。
另一个常见场景是把版本号拼接到人类可读的描述文本中,方便写入审计日志或导出文件。可以使用$concat配合$literal:
db.auditLogs.aggregate([
{
$project: {
event: 1,
description: {
$concat: [
{ $literal: 'MongoDB server version is ' },
{ $version: {} }
]
}
}
}
])
上面代码中$literal负责提供一个固定的字符串前缀,$version返回实际的服务器版本,两部分通过$concat拼接成一个完整描述。输出结果中的description字段值类似MongoDB server version is 5.0.14。这样的字段在排查环境问题时非常直观。
还可以结合$cond做能力判断。虽然版本号是字符串,但可以先通过$strcasecmp或$version与$literal比较来构造条件。不过更常见的是在应用层读取版本号后决定调用哪些API,因为聚合管道里的条件表达式相对有限。
三、分片集群与多实例环境下的注意事项
在分片集群环境中执行聚合时,$version返回的是具体处理该阶段的分片实例版本,而不是mongos的版本,也不是一个统一的集群版本。如果集群中不同分片运行了不同版本的MongoDB,那么同一批数据在不同分片上执行时可能会得到不同的版本字符串。例如主分片运行5.0.14,另一个分片运行5.0.17,聚合结果中就会同时出现这两种版本。
这对某些需要精确控制版本一致性的场景会带来困扰。例如将版本号写入审计表时,只凭一个$version字段无法判断整条管道在哪个实例上完成。解决办法是在应用层通过db.version()或buildInfo命令获取连接对象的版本,或者在每个分片单独执行聚合,再汇总结果。当然如果集群严格按照滚动升级流程维护,所有分片版本保持一致,那么$version返回的结果也就一致了。
另一个容易忽略的点是,$version只返回版本号本身,不包含企业版或社区版信息,也不包含补丁级别之外的安全修复编号。如果需要在日志中区分部署形态,应当使用db.serverBuildInfo()或者buildInfo命令中的modules、sysInfo等字段。但那些信息无法通过聚合表达式直接获得,通常需要在应用层补充。
四、常见错误与排查方法
最常见的问题是旧版本MongoDB不支持$version。在4.4以前的环境下运行包含该操作符的聚合,会抛出类似Unrecognized expression '$version'的错误。排查时先执行db.version()确认当前数据库版本,如果低于4.4,只能升级实例,或者改为在应用层获取版本信息后手动写入聚合输入。
第二个高频错误是参数写成非空文档。记住$version的调用形式必须是{ $version: {} },写成{ $version: 1 }、{ $version: '5.0' }或{ $version: true }都会触发解析失败。错误信息通常提示$version expects an empty document as its argument,根据这条提示可以直接定位。
第三个错误是混淆$version和$$version。在MongoDB聚合管道中,单个美元符号标志表达式,双美元符号标志系统变量或者用户自定义变量。如果写成$$version,MongoDB会尝试查找名为version的用户变量,没有定义时就会报Use of undefined variable: version。这个错误在代码审查阶段比较容易被发现,但在动态拼接管道的场景里偶尔会出现。
还有一个隐蔽问题是表达式位置使用不当。$version只能出现在表达式的值位置,例如字段值、条件分支或数组元素中,不能直接作为管道阶段使用。像下面这样把它当成一个阶段来写是错误的:
// 错误写法
db.users.aggregate([
{ $version: {} }
])
正确做法是把它放进$addFields、$project等表达式中。只要理解了操作符与管道阶段的区别,这类错误就能避免。
综合来看,$version操作符虽然功能单一,但在需要为聚合结果标记数据库版本的场景中非常实用。使用前确认实例版本不低于4.4,调用时严格使用空文档参数,再结合实际的分片部署情况,就能避免大部分使用误区。

