必须使用2dsphere索引支持真实地理距离计算,因其基于球面几何计算大圆距离(单位:米),正确处理跨本初子午线查询;而2d索引将地球视为平面,高纬度偏差严重。

如何用 2dsphere 索引支持真实地理距离计算
MongoDB 的地理位置查询必须依赖 2dsphere 索引,而不是旧的 2d。后者只适用于平面坐标(如笛卡尔坐标系),无法正确处理经纬度球面距离,查出来的“附近点”在赤道附近还凑合,一到高纬度就严重偏移。
建索引前确认字段结构:必须是 GeoJSON 格式({ type: "Point", coordinates: [lng, lat] }),或 legacy coordinate pair([lng, lat])。推荐用 GeoJSON,语义清晰且支持多类型(Polygon、LineString):
db.places.createIndex({ "location": "2dsphere" })注意:location 字段名要和实际文档中一致;如果字段不存在或格式错(比如 coordinates 写成 [lat, lng]),索引会建成功但查询返回空结果,且无报错提示。
$near 和 $geoNear 选哪个?
$near 是简单查询操作符,只能用在 find() 的 filter 中,不支持分页、排序混合、距离字段返回;$geoNear 是聚合阶段,功能完整但必须放在 pipeline 开头,且要求集合已建 2dsphere 索引。
常见误用:$near 直接写在 $or 或 $and 里会报错:near operator requires a single field。它必须是顶层 filter 的独立键。
实操建议:
- 只要查“最近的 N 个点”,用
$near最简:db.places.find({ location: { $near: { $geometry: { type: "Point", coordinates: [116.3974, 39.9093] }, $maxDistance: 1000 } } }) - 需要带距离字段、按距离排序、再加其他条件(如
type: "cafe")、或做分页,必须用$geoNear:db.places.aggregate([ { $geoNear: { near: { type: "Point", coordinates: [116.3974, 39.9093] }, distanceField: "dist", maxDistance: 1000, spherical: true } }, { $match: { type: "cafe" } } ])
$geoNear 的 spherical: true 必须显式设为 true(默认是 false),否则仍走平面计算。
为什么查出来距离不准?单位和坐标顺序是关键
MongoDB 所有地理距离默认单位是**米**,不是公里、不是度。如果你传 $maxDistance: 5,实际只查 5 米内——对大多数场景来说太小了,容易返回空。
另一个高频坑:GeoJSON 要求 coordinates 是 [longitude, latitude](先经度后纬度),但很多人从地图 API 拿到的是 [lat, lng],直接塞进去会导致点落在非洲西海岸之类离谱位置。
验证方法:
- 用
db.places.findOne({ location: { $exists: true } })看实际存的值 - 用在线工具(如 geojson.io)粘贴坐标,看是否落在预期城市
- 查一个已知距离的点,用
$geoNear输出dist字段,对比 Google Maps 测距
性能与数据量大的时候要注意什么?
2dsphere 索引本身不慢,但查询效率高度依赖 $maxDistance 或 limit。没设距离上限时,MongoDB 可能扫描大量无关文档再排序,响应从毫秒变秒级。
真实场景建议:
- 始终设置合理的
$maxDistance(比如 5000 米),哪怕前端允许用户拉远范围,也分页+多次请求,别让单次查全量 - 避免在
$geoNear后接$sort—— 它已经按距离排好序了,再$sort会触发内存排序,超限直接失败 - 如果常查“某区域内所有点”,考虑加复合索引:
db.places.createIndex({ "location": "2dsphere", "status": 1 }),配合$geoWithin加速
$geoNear 不支持 $text 索引共用,全文检索+地理筛选需拆成两步或用 Atlas Search。











