必须用 $geoNear 实现距离聚合,因 $collStats 不支持地理计算;$geoNear 必须为首阶段、配 2dsphere 索引、GeoJSON 坐标、near 和 distanceField 参数,并用 $bucket 分段统计。

用 $geoNear 替代 $collStats 做距离聚合
$collStats 根本不支持地理空间计算,它只返回集合元信息(如文档数、存储大小),和经纬度、距离完全无关。想按距离聚合,必须用 $geoNear —— 它是 MongoDB 唯一能同时做“地理位置排序 + 距离字段注入 + 聚合管道衔接”的阶段。
常见错误是把 $collStats 和 $geoNear 混在一起写,结果报错 "$geoNear is only valid as the first stage of a pipeline",因为 $geoNear 有硬性限制:必须是聚合管道的第一个阶段,且一个管道只能出现一次。
- 确保集合已为经纬度字段创建
2dsphere索引,例如:db.places.createIndex({ location: "2dsphere" }) -
location字段必须是 GeoJSON 格式({ type: "Point", coordinates: [lng, lat] }),不是普通数组或对象 - 别在
$geoNear后直接接$group就以为能统计——得先用$addFields提取距离值,再$group
$geoNear 必须指定 near 和 distanceField
少了这两个参数,$geoNear 直接报错或返回空。其中 near 是查询原点(用户当前位置),distanceField 是你希望 MongoDB 自动算好并挂到每条文档上的距离字段名。
注意单位:默认是米(distanceField 的值),但如果你传的 near 是 { type: "Point", coordinates: [116.3, 39.9] },而索引是 2dsphere,MongoDB 就按球面距离算;如果误建了 2d 索引,就会按平面欧氏距离算,结果偏差极大。
-
maxDistance单位也是米,设 1000 表示只取 1km 内的点 - 加
spherical: true显式声明球面计算(即使2dsphere索引也建议写上,避免旧版本歧义) - 别用
limit代替maxDistance控制范围——limit只截数量,不保距离精度
聚合统计距离分段(比如 0–1km、1–5km)要手动 $bucket
MongoDB 不提供内置“按距离区间分组计数”函数,得靠 $bucket 或 $switch 手动切段。直接 $group 按 distanceField 值分组会导致每个距离值一个桶,毫无统计意义。
典型写法是先 $geoNear 注入 dist 字段,再 $bucket 切区间。容易踩的坑是边界值处理:MongoDB 的 $bucket 默认包含下界、不包含上界([0, 1000)),但如果你的 boundaries 写成 [0, 1000, 5000],那 1000 米整会被分进第二段([1000, 5000)),而非第一段。
- 示例片段:
{ $bucket: { groupBy: "$dist", boundaries: [0, 1000, 5000, 10000], default: "other", output: { count: { $sum: 1 } } } } - 如果需要含等号的区间(如 ≤1000),改用
$switch+$lte更可控 -
$geoNear输出的dist是 double 类型,$bucket对 double 支持稳定,不用转类型
性能关键:索引命中和 maxDistance 范围控制
没 2dsphere 索引,$geoNear 会全表扫描,几万文档就明显卡顿;有索引但 maxDistance 设得过大(如 100km),实际仍可能扫大量索引项,响应变慢。
真实场景中,用户往往只关心“附近”,而不是“全市”。与其让数据库算完再过滤,不如在 $geoNear 阶段就用 maxDistance 把候选集压到几百条以内,后续 $group 或 $bucket 才真正轻量。
- 用
explain: true查看聚合执行计划,确认stage是GEO_NEAR_2DSPHERE而非COLLSCAN - 避免在
$geoNear后立刻$lookup关联大集合——距离计算本身已耗资源,关联会雪上加霜 - 如果只是要总数(不是明细),考虑用
countDocuments配合$geoWithin,比聚合快得多

















