$near 到底怎么查?结果对不对?这篇用 5 万个点对比 Python 的 haversine 暴力计算,看 explain 的逐圈搜索、多边形的索引键数,以及经纬度顺序写反会怎样。
test> db.places.createIndex({loc: "2dsphere"})
loc_2dsphere
test> db.places.getIndexes().filter(i => i.name != "_id_")
[
{
v: 2,
key: { loc: '2dsphere' },
name: 'loc_2dsphere',
'2dsphereIndexVersion': 4,
collation: { locale: 'simple' }
}
]
validate 的索引条目数: {'_id_': 50000, 'loc_2dsphere': 50000}2dsphere 索引为每个点生成一个条目,版本 4;GeoJSON 坐标顺序是 [经度, 纬度]。
$near(中心点, 2000 米) 返回 272 个;暴力计算 2000 米内有 272 个;_id 序列完全一致: True 最近的 5 个(_id, 暴力算出的距离米): [(17981, 88.4), (18246, 136.0), (44372, 273.2), (31564, 279.6), (31867, 288.2)] test> mongosh 里同样的 $near 查询的前 5 个 _id [ 17981, 18246, 44372, 31564, 31867 ]
2000 米内 $near 返回的 272 个点,与用 haversine 公式在 Python 里暴力计算的集合、顺序完全一致。
test> db.places.find({loc: {$near: ..., $maxDistance: 2000}})
{
plan: 'GEO_NEAR_2DSPHERE(loc_2dsphere)[FETCH > IXSCAN(loc_2dsphere) | FETCH > IXSCAN(loc_2dsphere) | FETCH > IXSCAN(loc_2dsphere)]',
nReturned: 272,
keysExamined: 452,
docsExamined: 381,
ms: 1
}
$near 2000 米 1.2 ms;$geoWithin/$centerSphere(用 2dsphere 索引)2000 米 0.8 ms;同样的 $geoWithin 强制全表 24 ms计划是 GEO_NEAR_2DSPHERE 加几段 FETCH + IXSCAN,由近到远逐圈扩展;检查 452 个键、381 个文档返回 272 个。强制全表的 $geoWithin 要 24 ms,用索引不到 1 ms。
test> db.places.aggregate([{$geoNear: {near: {type: "Point", coordinates: [121.47, 31.23]}, distanceField: "dist", spherical: true, maxDistance: 1000}}, {$limit: 3}, {$project: {name: 1, dist: {$round: ["$dist", 1]}}}])
[
{ _id: 17981, name: 'p17981', dist: 88.5 },
{ _id: 18246, name: 'p18246', dist: 136.1 },
{ _id: 44372, name: 'p44372', dist: 273.5 }
]聚合的 $geoNear 在结果里给出 distanceField,单位米。
small 单独放进一个集合,2dsphere 索引条目数 5 big 单独放进一个集合,2dsphere 索引条目数 20 point 单独放进一个集合,2dsphere 索引条目数 1 三个放在一起的索引条目数: 26 test> 哪些区域包含点 [121.405, 31.205] [ 'small', 'big' ] test> 哪些区域包含点 [121.5, 31.4] [ 'big' ]
多边形被 S2 单元覆盖,产生多个索引键:约 1 km² 的 5 条,约 2500 km² 的 20 条;点只有 1 条。
test> 数据在 [经度 10, 纬度 45] 附近;按 [10, 45] 查 5 km 内
125
test> 写反成 [45, 10](纬度 10、经度 45)—— 仍然合法,只是在另一个地方
0
test> 上海的 [纬度 31.23, 经度 121.47] 写反:纬度 121.47 越界,直接报错
invalid point in geo near query $geometry argument: { type: "Point", coordinates: [ 31.23, 121.47 ] } Longitude/latitude is out of bounds, lng: 31.23 lat: 121.
test> 插入纬度 95 的点
Could not extract geo keys at path 'loc' for document with _id ObjectId('6acc46c1aa3177504c05e95d'): Longitude/latitude is out of bounds, lng: 121.47 lat: 95写反了不一定报错:在合法范围内只是查不到(0 个),越界才报错。