MongoDB的聚合管道提供了一套丰富的表达式操作符,$avg就是其中一个高频使用的累加器。它可以在$group阶段对每组文档的某个字段求算术平均值,也可以在$project阶段直接对数组字段计算平均值。虽然在语法上并不复杂,但实际数据里经常混合着null、缺失字段或字符串,此时$avg的行为会直接影响统计结果。接下来从基本语法、脏数据规则以及底层执行机制三个角度展开。

$group累加器与$project数组操作的两种用法
在$group阶段,$avg的语法是{ $avg: <expression> },expression通常是一个字段路径,也可以是基于字段的简单表达式或$cond等复合表达式。下面的示例按部门分组,计算每个部门的平均薪资:
db.employees.aggregate([
{
$group: {
_id: "$department",
avgSalary: { $avg: "$salary" }
}
}
])
这里的$avg是文档级累加器,它会遍历组内每个文档,只把salary字段是数值类型的值纳入累加,最终返回double类型。如果组内没有任何文档拥有数值salary,该组的avgSalary会显示null。
除了$group,$avg还能作为数组表达式出现在$project中,用来对单个文档内的数组字段求平均。比如订单文档里items数组的每个元素都包含price,可以用以下管道直接得到每个订单的平均商品单价:
db.orders.aggregate([
{
$project: {
_id: 1,
avgItemPrice: { $avg: "$items.price" }
}
}
])
这里$items.price会先被解析成数组,再由$avg数组操作符计算平均值。需要注意的是,这两种$avg虽然同名,但适用阶段不同:在$group中接收的表达式每次作用于一个文档的字段值;在$project中则接收数组字段并返回数组元素的平均值。混用阶段会直接报错,比如在$project里写{ $avg: "$salary" }且salary不是数组,MongoDB会尝试把标量视为单元素数组,虽然可能不报错但语义不符,所以要根据场景选择正确写法。
null、缺失字段与非数值类型的忽略规则
实际数据集很少完全干净,$avg对脏数据的处理可以用三个词概括:忽略null、忽略缺失、忽略非数值。也就是说,如果文档中的字段值为null、不存在,或者类型是字符串、布尔值、数组等非数值类型,$avg都会把它排除在计算之外。看下面这个包含脏数据的集合:
db.salaries.insertMany([
{ name: "张三", salary: 8000 },
{ name: "李四", salary: null },
{ name: "王五" },
{ name: "赵六", salary: "9000" },
{ name: "钱七", salary: 12000 }
])
执行按全表分组的$avg计算:
db.salaries.aggregate([
{
$group: {
_id: null,
avgSalary: { $avg: "$salary" }
}
}
])
结果会是10000,因为只有8000和12000被计入,(8000 + 12000) / 2 = 10000。null、缺失字段、字符串都被忽略。如果所有文档的salary都缺失或都不是数值,$avg返回null。这种忽略规则也适用于数组操作符:当数组元素中出现null或非数值时同样跳过,但空数组或全部非数值时返回null。
如果业务上需要把缺失字段当作0参与平均,或者把null显式转换成0,可以先用$ifNull或$cond处理。例如把没有salary的文档视为0:
db.salaries.aggregate([
{
$group: {
_id: null,
avgSalary: {
$avg: { $ifNull: ["$salary", 0] }
}
}
}
])
此时字符串"9000"仍然会被忽略,如果想包含它需要先用$toDouble转换。条件平均也很常见,比如只统计在职员工的平均薪资,可以在$avg内部用$cond返回薪资或null,因为$avg会忽略null,也可以返回0但要注意这样会拉低平均值。推荐返回null保持忽略语义:
db.employees.aggregate([
{
$group: {
_id: "$department",
avgActiveSalary: {
$avg: {
$cond: [
{ $eq: ["$status", "active"] },
"$salary",
null
]
}
}
}
}
])
这里$cond的then分支返回薪资,else分支返回null,$avg自动忽略null,从而只计算active员工的平均薪资。理解这些忽略规则是避免统计偏差的关键。
分片集群下的执行机制与浮点精度调优
在分片集群中,$group可能分布在多个分片并行执行。为了不传输所有原始文档,MongoDB会让每个分片先计算局部sum和count,然后mongos或主分片合并这些结果,再用总sum除以总count得到最终平均值。这种方式既减少了网络传输量,也保证了最终结果与单机计算完全一致,不会出现先平均再平均造成的权重偏差。例如两个分片分别有3个和5个薪资值,合并时使用分片的sum和count计算总平均,而不是简单对两个分片的平均值再平均。
不过$avg的返回值类型是double,累加过程中如果数值是整型但很大,超过double的精确表示范围,就会产生精度损失。MongoDB内部在处理$avg时会根据输入类型选择整数累加或浮点累加,但最终统一为double。对于需要高精度的场景,可以先使用$sum计算精确的整数总和,再配合$divide做除法,但要注意$divide仍然返回double。如果要求精确到小数后几位,可以在应用层格式化,或者使用Decimal128类型,但聚合表达式对Decimal128的支持有限。
性能方面,$avg本身的计算开销通常低于$push等需要保留原始数据的操作器,因为$avg只需要维护sum和count两个内部状态。优化聚合管道时,应尽量在$group之前使用$match过滤无关文档,并利用索引减少扫描范围。如果数据量很大且$group涉及大量不重复分组键,内存可能不足,此时可以为aggregate命令设置allowDiskUse: true,让MongoDB把临时数据写入磁盘。另外,使用$sort和$limit时注意顺序,例如不用在$group后再对所有组排序,而应使用$sort在管道末尾只处理需要的组。
与手动写$sum和$divide相比,$avg可读性更好,行为上两者等价,但手动写法需要额外维护计数,容易出错,因此除非有特殊精度需求,推荐直接使用$avg。理解这些内部机制后,在报表统计、实时监控和数据分析等场景里就能更放心地使用它。
MongoDB聚合管道$avg平均值计算修改时间:2026-09-26 10:04:02