在做数据统计的时候,我们经常需要把数值按照某个周期拆分,比如按奇偶数分组、按每100条数据分一桶,或者筛选出能被某个数整除的记录。这类需求在SQL里通常用取模运算符%来完成,而到了MongoDB的聚合管道里,对应的操作符就是$mod。它可以把两个字段的值或者常量做取模运算,返回除法运算后的余数,然后配合$group、$match等阶段实现各种统计和筛选逻辑。这篇文章就来详细聊聊$mod的语法、用法以及实际使用中需要注意的细节。

$mod操作符的基本语法与参数说明
$mod是MongoDB聚合管道中的一个算术表达式操作符,它的语法格式是数组形式的:{ $mod: [ <表达式1>, <表达式2> ] }。数组里第一个元素是被除数,第二个元素是除数,运算结果就是表达式1除以表达式2之后得到的余数。两个元素既可以是数字字面量,也可以是字段引用,还可以是嵌套的其他表达式。
举个例子,假设有一个存储商品数据的集合products,每条文档里有一个price字段,我们想在投影阶段计算价格对500取模的结果,可以这样写:
db.products.aggregate([
{
$project: {
name: 1,
price: 1,
remainder: { $mod: [ "$price", 500 ] }
}
}
])
如果某条文档的price是1320,那么remainder的结果就是320。需要注意的是,$mod返回的余数符号遵循被除数的符号,这一点和数学上的取模定义以及某些编程语言的行为不同。比如{ $mod: [ -10, 3 ] }的结果是-1,而不是2。这个特性在处理负数字段时一定要格外小心,否则统计结果可能出现意料之外的偏差。
结合$match和$group实现奇偶筛选与分桶统计
单独使用$mod意义不大,它的价值主要体现在配合其他管道阶段的场景中。最常见的一个用法是判断奇偶性。比如我们想筛选出集合中id为偶数的用户记录,可以在$match阶段配合$expr表达式来实现:
db.users.aggregate([
{
$match: {
$expr: { $eq: [ { $mod: [ "$userNo", 2 ] }, 0 ] }
}
}
])
这条查询会把userNo能被2整除的文档全部筛出来。这里必须使用$expr,因为普通的查询语法不支持聚合表达式,直接写{ $mod: [...] }作为查询条件会被当作查询操作符的语法处理,含义完全不同,这一点后面会详细说明。
另一个典型场景是数据分桶统计。假设订单集合orders里每条记录有金额字段amount,我们想统计金额落在0到99、100到199、200到299这样每100一个区间的订单数量,用$mod结合除法和向下取整就能实现:
db.orders.aggregate([
{
$group: {
_id: {
$floor: {
$divide: [ { $subtract: [ "$amount", { $mod: [ "$amount", 100 ] } ] }, 100 ]
}
},
count: { $sum: 1 },
totalAmount: { $sum: "$amount" }
}
},
{ $sort: { _id: 1 } }
])
这段聚合先通过$mod计算amount对100的余数,再用原值减去余数得到区间下限,除以100后就得到了桶编号,最后按桶编号分组计数并汇总金额。虽然也可以用$bucket自动分桶,但当桶边界不规则或者需要更灵活的计算逻辑时,$mod的组合写法往往更可控。
使用$mod时容易踩的几个坑
第一个坑是把聚合表达式操作符$mod和查询操作符$mod搞混。在普通查询中,$mod是一个条件操作符,语法是{ field: { $mod: [除数, 余数] } },用来匹配字段值除以某个数后余数等于指定值的文档。而在聚合管道里,$mod是算术运算符,作用是计算并返回余数。两者形式相似但语义完全不同,如果在$match里想用聚合表达式,必须借助$expr包装,否则会直接报错。
第二个坑是除数为零或字段缺失的问题。如果$mod的第二个参数计算结果为0,MongoDB会抛出错误,提示不能除以零。因此在除数来自字段值或者经过计算得出时,最好先用$cond或者$ifNull做防御性处理。而如果被除数字段在某条文档中不存在,结果会是null;如果字段类型不是数字而是字符串,在严格模式下也会返回null或者触发类型错误。生产环境中数据类型往往不干净,写聚合语句前先用$type检查一下字段类型分布是个好习惯。
第三个坑涉及性能。在$match中通过$expr使用$mod进行筛选时,MongoDB无法利用普通索引,因为索引存储的是原始字段值而不是表达式结果。如果集合数据量很大,这种查询会触发全集合扫描,性能会明显下降。应对办法有两种:一是尽量把$match放到管道最前面并附加其他能走索引的过滤条件,缩小扫描范围;二是在写入时就把取模结果冗余存储为一个独立字段并建立索引,查询时直接匹配该字段。此外,对于整数运算,如果操作数是double类型且数值过大,可能出现浮点精度问题导致余数不准确,涉及金额等精度敏感的场景建议使用Decimal128类型存储,或者把取模计算放到应用层完成。
总的来说,$mod本身并不复杂,真正决定查询质量的是对数据分布、类型和索引情况的把握。掌握它的符号规则和与$expr的配合方式,再结合分桶、奇偶筛选这些典型模式,就能在聚合管道里灵活地处理各类按余数拆分数据的业务需求。
MongoDB聚合管道$mod取模取模运算修改时间:2026-09-04 15:22:38