MongoDB的空间查询能力一直是地理位置类应用的核心支撑,无论是查找附近的门店、计算配送范围,还是做电子围栏判断,都离不开它。但很多人在写查询语句时会被距离单位搞得晕头转向:明明想查500米范围内的点,结果查出来的是几十公里外的数据,或者干脆一条都查不到。问题的根源通常不在索引或语法,而在于没有搞清楚MongoDB在不同索引类型、不同坐标格式下使用的距离单位。本文围绕空间查询的单位问题展开,把2d索引与2dsphere索引、legacy坐标与GeoJSON坐标、以及各类距离操作符的单位规则讲清楚,并配以可运行的示例。

一、MongoDB空间查询的两种索引与两套单位体系
MongoDB支持两种空间索引:2d索引和2dsphere索引。二者的本质区别在于数据模型:2d索引把坐标当作平面上的点来处理,适合游戏地图、平面示意图这类没有球面曲率的场景;2dsphere索引则基于球面几何,专门用于真实的地球经纬度数据,底层支持GeoJSON对象,包括Point、LineString、Polygon等。
正是这个区别决定了单位体系的不同。在2dsphere索引下,所有距离参数的单位统一是米,MongoDB内部会根据地球半径完成球面距离换算,开发者不需要做任何转换。而在2d索引下,坐标是纯粹的平面坐标,MongoDB不做任何单位假设,距离参数的单位就是与坐标相同的单位。如果你的坐标存的是经纬度(即度),那么传入的距离参数也必须先从米换算成度,或者更常见的做法是换算成弧度。
这里有一个非常经典的坑:把经纬度数据存进2d索引,然后直接用米作为$maxDistance的值。由于经纬度数值范围只有-180到180,传一个500这样的距离值,相当于查询几乎整个坐标系,自然会把全世界的数据都查出来。反过来,如果单位算小了,就可能一条数据都查不到。
二、legacy坐标与GeoJSON坐标:顺序和单位的差异
同样的一个地理位置点,在MongoDB里有两种表达方式。第一种是legacy坐标对,即一个二元数组:
{
name: "门店A",
location: [116.404, 39.915] // 注意顺序:经度在前,纬度在后
}第二种是GeoJSON格式:
{
name: "门店A",
location: {
type: "Point",
coordinates: [116.404, 39.915] // 同样是经度在前
}两种写法都必须遵守经度在前、纬度在后的顺序,这一点和许多GIS系统相反,是新手最容易犯的错之一。顺序写反后,数据会落到完全错误的位置,查询结果自然全错,而且MongoDB不会报任何错误。
更关键的是,同一个操作符在两种格式下的单位表现不同。以$near为例,如果查询条件用legacy坐标对并且集合是2d索引,距离单位就是弧度(或与坐标一致的平面单位);如果用GeoJSON的Point且集合是2dsphere索引,那么$maxDistance的单位直接就是米。也就是说,下面两条查询虽然长得像,单位含义完全不同:
// 2dsphere 索引 + GeoJSON 格式,单位是米,查500米内
db.places.find({
location: {
$near: {
$geometry: {
type: "Point",
coordinates: [116.404, 39.915]
},
$maxDistance: 500
}
}
})
// 2d 索引 + legacy 坐标对,第二个参数单位是弧度
db.places.find({
location: {
$near: [116.404, 39.915],
$maxDistance: 500 / 6378100 // 米换算成弧度:除以地球半径
}
})弧度换算的公式很简单:地球平均半径约6378100米(也有人用6371000米,差别不到千分之一),弧度 = 米 ÷ 地球半径。反过来,如果要把查询结果的距离从弧度换算成米,就乘以地球半径。
三、$geoNear聚合阶段与distanceMultiplier的使用
当需要在查询结果中直接拿到距离值时,$geoNear聚合阶段是最方便的工具。它可以输出一个dis字段表示距离,但这个距离的单位同样取决于输入格式:如果near参数用的是GeoJSON Point且索引是2dsphere,dis的单位是米;如果用的是legacy坐标对,dis的单位则是弧度,需要自己换算。
为了让输出直接变成米,$geoNear提供了distanceMultiplier参数,它会把dis字段乘以这个系数。下面是一个完整的例子,查询某坐标附近1000米内的地点,并直接以米为单位输出距离:
db.places.aggregate([
{
$geoNear: {
near: { type: "Point", coordinates: [116.404, 39.915] },
key: "location",
distanceField: "distance", // 结果中距离存放的字段名
maxDistance: 1000, // 单位:米
distanceMultiplier: 1, // GeoJSON输入时单位已是米
spherical: true,
query: { status: "active" } // 可叠加普通过滤条件
}
}
])如果因为历史原因必须使用legacy坐标对,可以这样写:
db.places.aggregate([
{
$geoNear: {
near: [116.404, 39.915],
distanceField: "distance",
maxDistance: 1000 / 6378100, // 米转弧度
distanceMultiplier: 6378100, // 输出时弧度转米
spherical: true
}
}
])使用$geoNear时有几点需要注意。第一,它必须是聚合管道的第一个阶段。第二,一个集合只能有一个2d或2dsphere索引时可以不指定key,如果存在多个空间索引,必须通过key参数指明使用哪个字段。第三,$geoNear返回的结果天然按距离从近到远排序,不需要再额外加$sort。
四、常见单位错误与排查思路
实际开发中,单位问题引发的现象主要有三类,可以对照排查。第一类是查出来的结果明显过远,通常是2d索引下把米当成了坐标单位使用,解决办法是改成弧度或迁移到2dsphere。第二类是一条结果都查不到,可能是坐标顺序写反,或者索引类型与查询格式不匹配,例如对2d索引用了$geometry参数。第三类是距离值数量级异常,比如结果里dis是0.0001这样的小数,说明输出单位是弧度,乘以地球半径即可得到米。
另外还有几个容易忽略的细节。$minDistance和$maxDistance一样,单位规则也随索引类型变化,2dsphere下是米,2d下是弧度。对于球面查询,官方推荐始终使用2dsphere索引加GeoJSON格式,这样整个链路的单位统一为米,维护成本最低。2d索引和legacy坐标对属于遗留方案,新项目不建议使用。
最后建议在数据建模阶段就定好规范:所有位置字段统一命名为location,统一使用GeoJSON Point格式,统一建立2dsphere索引。这样后续所有空间查询的单位都是米,团队协作时不会再有人误用弧度,从源头上避免单位混乱的问题。如果项目里已经存在legacy数据,可以写一个批量脚本把坐标对转换为GeoJSON结构,再重建索引,一次性完成单位体系的统一。
MongoDB空间查询地理索引geoNear修改时间:2026-09-03 00:26:59