MongoDB做地理位置检索时,最常见的写法是在find里用$near或$nearSphere,配合$maxDistance限制搜索半径。但一旦需求变复杂,需要统计、分组、关联其他集合,就得切换到聚合管道,这时候直接把$maxDistance塞进$match里是查不出结果的,甚至直接报错。本文围绕聚合管道中的距离筛选展开,讲清楚$geoNear阶段的正确用法和容易踩的坑。

为什么$match里用$maxDistance会失效
普通查询中,$near本身就是一个独立的查询操作符,它和$maxDistance、$minDistance是配套使用的,MongoDB会利用地理空间索引直接完成距离过滤和排序。但聚合管道中$match只是普通查询的镜像,MongoDB明确禁止在$match中使用$near,执行时会抛出类似无法执行查询的错误提示,$nearSphere在聚合中同样受限。
原因是聚合管道有专门为地理检索设计的阶段——$geoNear。官方要求所有涉及距离计算和过滤的逻辑都放到这个阶段完成,而且$geoNear必须是管道的第一个阶段,前面不能有$match、$project等任何阶段,否则会直接报错中断执行。
所以正确的思路是:用$geoNear的maxDistance参数替代原来的$maxDistance操作符。注意写法上的区别,普通查询里它是带$前缀的操作符,而$geoNear里它是普通的字段参数,值直接传数字。
$geoNear的maxDistance正确写法
$geoNear要求集合上存在2dsphere或2d索引,先给坐标字段建索引:
// 建立球面地理索引
db.places.createIndex({ location: "2dsphere" })然后构造聚合查询,假设要查找距离坐标点(116.397, 39.909)五公里以内的地点:
db.places.aggregate([
{
$geoNear: {
near: { type: "Point", coordinates: [116.397, 39.909] },
key: "location",
distanceField: "dist.calculated",
maxDistance: 5000, // 最大距离,单位米
spherical: true,
query: { status: "active" } // 可选的附加过滤条件
}
},
{ $limit: 20 }
])几个参数必须理解到位。distanceField指定计算出的距离写入哪个字段,上面的写法会在每个输出文档里生成dist.calculated字段,后续阶段可以直接用它做排序或筛选。maxDistance的单位取决于坐标系:使用GeoJSON点坐标时单位是米,使用传统坐标对时单位是弧度,这是最容易出错的地方。
如果想让距离值直接以公里输出,可以加distanceMultiplier参数设为0.001,这样输出的dist.calculated就是公里数,而maxDistance仍然按米传值,两者互不影响。
球面坐标与平面坐标的单位陷阱
当near传的是GeoJSON对象时,MongoDB按WGS84球面计算,maxDistance单位是米,这也是生产环境最推荐的方式。但如果near直接传一个坐标数组,走的就是平面计算逻辑,此时maxDistance的单位不再是米,而是弧度,这让很多初学者困惑。
弧度和米的换算公式是:弧度等于米除以地球半径。地球平均半径约6378137米,例如要筛选1000米范围,对应的弧度值约为1000除以6378137,约等于0.0001569。如果你按米传值而实际走的是弧度逻辑,maxDistance会大到基本没有过滤效果,查询出来的结果远超预期范围。
避免这个坑的办法很简单:统一使用GeoJSON格式加2dsphere索引,明确传type和coordinates,让单位始终是米。只有处理一些历史遗留的平面数据(比如游戏内平面地图坐标)时才考虑传统坐标对写法。
与$near写法的对比及常见报错排查
把两种写法放在一起对比更容易记忆。普通查询版本:
// find写法,操作符带$前缀
db.places.find({
location: {
$nearSphere: {
$geometry: { type: "Point", coordinates: [116.397, 39.909] },
$maxDistance: 5000
}
}
})聚合版本则是参数式写法,maxDistance不带$前缀,且必须配合distanceField一起使用。此外还有一个差别:$geoNear输出结果默认按距离从近到远排好序,后续不需要再写$sort,除非你想按其他字段重新排序。
常见报错有三类。第一类提示$geoNear阶段需要索引,说明目标字段没有建地理索引,先执行createIndex即可。第二类提示$geoNear不在管道首位,说明前面放了别的阶段,把它挪到第一个就行。第三类是查不到数据但不报错,大概率是单位问题,按上面的换算方式检查maxDistance传的是米还是弧度,或者检查坐标顺序是否写反——GeoJSON要求经度在前纬度在后,国内坐标常见格式是[经度, 纬度],一旦写反,距离会算到几万公里之外,自然查不到任何结果。
最后补充一点,$geoNear里还可以用minDistance设置最小距离,实现环形检索,比如查找500米到2000米之间的门店,minDistance和maxDistance配合使用即可,两个参数的单位规则完全一致。
MongoDB聚合管道maxDistance修改时间:2026-09-05 23:17:07