必须使用2dsphere索引实现球面距离计算,而非2d索引;坐标须为[经度,纬度]格式并配$geometry和$minDistance/$maxDistance(单位米);分页需用游标式查询而非skip。

MongoDB 地理位置索引必须用 2dsphere,不是 2d
Node.js 里用 Mongoose 或原生 driver 查“附近的人”,第一步就容易错在索引类型上。2d 只支持平面坐标(经纬度当笛卡尔坐标算),地球曲率一忽略,5km 以外距离误差能到几百米;而真实场景下你存的是 GeoJSON 点({ type: "Point", coordinates: [lng, lat] })或 legacy coordinate pair,必须配 2dsphere 索引才走球面距离计算。
常见错误现象:geoNear 返回空、$near 报 error: 'can't find a valid index for geo query'、或者结果明显偏离(比如查 1km 却返回 5km 外的用户)。
- 建索引命令必须是:
db.users.createIndex({ location: "2dsphere" }),其中location是你存坐标的字段名 - Mongoose 中定义 Schema 时,字段要显式设
type: { type: String, enum: ["Point"], default: "Point" }+coordinates: { type: [Number], required: true },再调userSchema.index({ location: "2dsphere" }) - 别把
location字段直接设成[Number, Number]数组——MongoDB 不认这是地理数据,2dsphere索引会建成功但查询不生效
$near 查询必须配合 $maxDistance(单位是米)
只写 $near 不加距离限制,MongoDB 默认返回最近的 100 条,但不会过滤远点——它只是按距离排个序,后端还得自己遍历裁剪,既慢又不准。真正“附近”得靠 $maxDistance 做服务端硬过滤。
注意:这个距离单位固定是米,不是 km,不是 mile,传 1000 就是 1km;而且它只接受数字,不能传字符串或变量不 resolve 就塞进去。
- 正确写法(Mongoose):
users.find({ location: { $near: { $geometry: { type: "Point", coordinates: [116.4, 39.9] }, $maxDistance: 1000 } } }) - 错误写法:
$maxDistance: "1000"(字符串)、$maxDistance: 1(以为是 km)、或者漏掉$geometry直接写坐标数组(老版本 legacy 格式已弃用,v4.4+ 默认不支持) - 性能影响:没
$maxDistance时,MongoDB 可能扫描大量文档排序;加了之后能利用索引快速剪枝,QPS 高时尤其明显
Node.js 侧传入坐标顺序必须是 [经度, 纬度],反了就全偏移
MongoDB 的 GeoJSON 规范强制要求 coordinates 是 [longitude, latitude],和日常说“北纬东经”顺序相反。前端用高德/百度地图 API 拿到的 lat, lng 对,直接塞进查询就会东西颠倒——北京查出来可能在大西洋。
常见错误现象:明明用户在北京朝阳区,查询返回的却是东京或纽约的用户;或者所有结果集中在某条经线/纬线上,呈带状分布。
- 后端收到前端坐标后,务必先交换顺序:
const [lng, lat] = [req.body.lat, req.body.lng]→ 错!应为const [lng, lat] = [req.body.lng, req.body.lat] - 调试时打印出实际发给 MongoDB 的
$geometry值,确认是[116.4, 39.9]而非[39.9, 116.4] - 如果用 Mapbox 或其他国际地图 SDK,它们默认就是
[lng, lat],但国内 SDK(如高德)多数是[lat, lng],得看文档,不能凭经验猜
分页查“附近的人”不能用 skip,得用 $minDistance + 游标
用户往下拉加载更多,第一反应是 .skip(20).limit(20),但这在地理查询里极危险:跳过前 20 个最近的,第 21 个可能是 800m 外的,而 200m 内其实还有 50 个没被扫到——skip 是按排序后结果跳,不是按空间范围跳。
真要分页,得记住上一页最后一个人的距离,下一页查“比这个距离更远、且仍在 $maxDistance 内”的人。
- 第一次查:
{ location: { $near: { $geometry: [...], $maxDistance: 1000 } } } - 第二页查(假设上一页最后一个距离是 320 米):
{ location: { $near: { $geometry: [...], $minDistance: 320, $maxDistance: 1000 } } } - 关键点:
$minDistance和$maxDistance必须同时出现,且单位一致(都是米);单独用$minDistance会报错 - 客户端需保存上一页结果中最大的
distance字段值(MongoDB 在geoNear聚合里会返回,普通find不返回,得用聚合管道加$geoNear阶段)
这事没表面看起来简单——距离值受地球曲率、投影方式、MongoDB 版本微小差异影响,两次查询之间用户移动、新用户加入都会让游标失效。生产环境建议加一层缓存或降级逻辑,别死磕绝对精确。










