$match: { a: "$b" } 不起作用是因为它将 "$b" 视为字面量字符串而非字段引用;需改用 $expr(如 { $match: { $expr: { $eq: ["$a", "$b"] } } })实现字段间比较,但 $expr 不走索引。

为什么直接写 $match: { a: "$b" } 不起作用
因为 $match 默认只支持“字段值与常量比较”,不解析字符串里的 $ 前缀——"$b" 被当成字面量字符串,不是对字段 b 的引用。MongoDB 会安静地匹配所有 a 字段值恰好等于字符串 "$b" 的文档,而不是取 b 字段的值来比。
$expr 是唯一能做同文档字段间比较的入口
$expr 允许你在聚合管道中使用聚合表达式(比如 $eq、$gt),这些表达式能正确解析 $field 语法,访问当前文档的字段值。
常见写法示例:
{
$match: {
$expr: { $eq: ["$a", "$b"] }
}
}
注意点:
-
$eq的两个参数都必须是数组形式:["<code>$a", "$b"],不能写成{ $eq: ["$a", "$b"] }(这是对的)或{ $eq: "$a", "$b" }(语法错误) - 字段名前面的
$不能漏,否则会被当常量处理 -
$expr无法使用索引加速(除非配合$and把其他条件抽出去),大数据量时性能敏感
字段类型不一致会导致 $eq 返回 false(即使值看起来一样)
MongoDB 的字段比较严格区分类型:字符串 "123" 和数字 123 永远不等,null 和缺失字段也不同。
如果不确定字段类型是否统一,得显式转换:
{
$match: {
$expr: { $eq: [{ $toString: "$a" }, { $toString: "$b" }] }
}
}
可用的类型转换操作符包括:$toString、$toInt、$toDecimal、$toDate。选哪个取决于你期望的比较语义。
另外注意:$toInt 遇到非数字字符串会报错并让整个文档被过滤掉(不是跳过转换),生产环境慎用。
想在 $match 里同时用索引和字段比较?得拆开写
$expr 自身不走索引,但你可以把能走索引的条件(比如 status: "active")和字段比较逻辑分开:
[
{ $match: { status: "active" } },
{ $match: { $expr: { $gt: ["$price", "$cost"] } } }
]
这样第一阶段能利用 status 索引快速缩小数据集,第二阶段才做字段计算。别贪图合并成一个 $match——没收益,还可能误导自己以为有索引优化。
真正容易被忽略的是:字段比较逻辑一旦涉及 $expr,就彻底脱离了查询优化器的索引决策路径。别指望加个复合索引就能让它变快。


















