【问题标题】:Efficiently sorting the results of a mongodb geospatial query有效地对 mongodb 地理空间查询的结果进行排序
【发布时间】:2023-03-29 10:35:02
【问题描述】:

我有大量的文档,例如:

{ loc: [10.32, 24.34], relevance: 0.434 }

并且希望能够高效地执行如下查询:

 { "loc": {"$geoWithin":{"$box":[[-103,10.1],[-80.43,30.232]]}} }

带有任意框。

loc 上添加一个二维索引使这非常快速和高效。但是,我现在也想获取最相关的文件:

.sort({ relevance: -1 })

这导致一切都变得一团糟(在任何特定的框中都可能有大量结果,我只需要前 10 个左右)。

非常感谢任何建议或帮助!

【问题讨论】:

标签: mongodb


【解决方案1】:

您是否尝试过使用聚合框架?

两阶段管道可能有效:

  1. 一个使用现有 $geoWithin 查询的 $match 阶段。
  2. 一个按relevance: -1排序的$sort阶段

以下是它的外观示例:

db.foo.aggregate(
    {$match: { "loc": {"$geoWithin":{"$box":[[-103,10.1],[-80.43,30.232]]}} }},
    {$sort: {relevance: -1}}
);

我不确定它会如何执行。然而,即使它在 MongoDB 2.4 中表现不佳,但在 2.6/2.5 中可能会大不相同,因为 2.6 将包含 improved aggregation sort performance

【讨论】:

  • 感谢您在 aggregate() 函数中展示 $geoWithin 的示例。这是我能找到的唯一例子。我注意到在使用 $sort 时,使用 aggregate() 和 find() 函数的速度没有区别。但如果不包括排序,find() 的执行速度要比 $aggregate 快得多。我使用的是 MongoDB 2.4.9 版。 2.6的好资料。等 mongoLabs 升级我会试试看。
【解决方案2】:

当有一个巨大的结果匹配特定的盒子时,排序操作真的很昂贵,所以你肯定想避免它。 尝试在相关字段上创建单独的索引并尝试使用它(根本没有 2d 索引):这样查询将更有效地执行 - 文档(已经按相关性排序)将被逐一扫描,以匹配给定的地理框条件。当找到前 10 名时,你就很好了。

不过,如果地理框只匹配集合的一小部分,它可能不会那么快。在最坏的情况下,它需要扫描整个集合。

我建议您创建 2 个索引(位置与相关性)并对应用中常见的查询运行测试(使用 mongo 的 hint 强制使用所需的索引)。

根据您的测试结果,您甚至可能想要添加一些应用程序逻辑,以便如果您知道该框很大,您可以使用相关索引运行查询,否则使用 loc 2d 索引。只是一个想法。

【讨论】:

  • 谢谢,这两种类型的查询都非常常见 - 并且要知道该区域是否将包含大量文档或少量文档实际上是不可能/不可行的。
  • 我想知道的一件事是,我是否可以同时触发两个查询(每个使用不同的索引),并且一旦一个返回 - 终止另一个?
  • 我不知道您有任何可能从客户端触发查询并获取其操作 ID,以便您可以对其调用 killOp。即使它存在,我想你会得到你的 mongo 实例的双重负载,因为当你从获胜查询中获取结果时,获取丢失查询的 op id,发送请求以杀死它,它已经消耗了计算资源 +那些自己管理查询的人仍然需要 cpu。
【解决方案3】:

当您尝试使用对复合键部分进行排序时,您不能将扫描和订单值设为 0。不幸的是,目前没有解决您的问题的方法,这与您使用二维索引的现象无关。

当您对查询运行解释命令时,“scanAndOrder”的值显示天气是否需要在收集结果后进行排序。如果是真的,则需要在查询后进行排序,如果是不需要错误排序。

为了测试这种情况,我以这种方式在示例数据库中创建了一个名为 t2 的集合:

db.createCollection('t2')
db.t2.ensureIndex({a:1})
db.t2.ensureIndex({b:1})
db.t2.ensureIndex({a:1,b:1})
db.t2.ensureIndex({b:1,a:1})

for(var i=0;i++<200;){db.t2.insert({a:i,b:i+2})}

虽然您只能使用 1 个索引来支持查询,但我做了以下测试,结果包括:

mongos> db.t2.find({a:{$gt:50}}).sort({b:1}).hint("b_1").explain()
{
    "cursor" : "BtreeCursor b_1",
    "isMultiKey" : false,
    "n" : 150,
    "nscannedObjects" : 200,
    "nscanned" : 200,
    "nscannedObjectsAllPlans" : 200,
    "nscannedAllPlans" : 200,
    "scanAndOrder" : false,
    "indexOnly" : false,
    "nYields" : 0,
    "nChunkSkips" : 0,
    "millis" : 0,
    "indexBounds" : {
        "b" : [
            [
                {
                    "$minElement" : 1
                },
                {
                    "$maxElement" : 1
                }
            ]
        ]
    },
    "server" : "localhost:27418",
    "millis" : 0
}
mongos> db.t2.find({a:{$gt:50}}).sort({b:1}).hint("a_1_b_1").explain()
{
    "cursor" : "BtreeCursor a_1_b_1",
    "isMultiKey" : false,
    "n" : 150,
    "nscannedObjects" : 150,
    "nscanned" : 150,
    "nscannedObjectsAllPlans" : 150,
    "nscannedAllPlans" : 150,
    "scanAndOrder" : true,
    "indexOnly" : false,
    "nYields" : 0,
    "nChunkSkips" : 0,
    "millis" : 1,
    "indexBounds" : {
        "a" : [
            [
                50,
                1.7976931348623157e+308
            ]
        ],
        "b" : [
            [
                {
                    "$minElement" : 1
                },
                {
                    "$maxElement" : 1
                }
            ]
        ]
    },
    "server" : "localhost:27418",
    "millis" : 1
}
mongos> db.t2.find({a:{$gt:50}}).sort({b:1}).hint("a_1").explain()
{
    "cursor" : "BtreeCursor a_1",
    "isMultiKey" : false,
    "n" : 150,
    "nscannedObjects" : 150,
    "nscanned" : 150,
    "nscannedObjectsAllPlans" : 150,
    "nscannedAllPlans" : 150,
    "scanAndOrder" : true,
    "indexOnly" : false,
    "nYields" : 0,
    "nChunkSkips" : 0,
    "millis" : 1,
    "indexBounds" : {
        "a" : [
            [
                50,
                1.7976931348623157e+308
            ]
        ]
    },
    "server" : "localhost:27418",
    "millis" : 1
}


 mongos> db.t2.find({a:{$gt:50}}).sort({b:1}).hint("b_1_a_1").explain()
{
    "cursor" : "BtreeCursor b_1_a_1",
    "isMultiKey" : false,
    "n" : 150,
    "nscannedObjects" : 150,
    "nscanned" : 198,
    "nscannedObjectsAllPlans" : 150,
    "nscannedAllPlans" : 198,
    "scanAndOrder" : false,
    "indexOnly" : false,
    "nYields" : 0,
    "nChunkSkips" : 0,
    "millis" : 0,
    "indexBounds" : {
        "b" : [
            [
                {
                    "$minElement" : 1
                },
                {
                    "$maxElement" : 1
                }
            ]
        ],
        "a" : [
            [
                50,
                1.7976931348623157e+308
            ]
        ]
    },
    "server" : "localhost:27418",
    "millis" : 0
}

单个字段上的索引没有多大帮助,所以 a_1(不支持排序)和 b_1(不支持 queryin)是 out 。 a_1_b_1 上的索引也不幸运,虽然它的性能比单个 a_1 差,但 mongoDB 引擎不会利用与存储的一个 'a' 值相关的部分以这种方式排序的情况。值得尝试的是复合索引 b_1_a_1 ,在您的情况下,relevant_1_loc_1 虽然它会以有序的方式返回结果,因此 scanAndOrder 将是错误的,我还没有测试 2d 索引,但我认为它会排除扫描仅基于索引值(这就是为什么在这种情况下的测试中 nscanned 高于 nscannedObjects)。不幸的是,索引会很大,但仍比文档小。

【讨论】:

    【解决方案4】:

    如果您需要在框(矩形)内搜索,此解决方案有效。

    地理空间索引的问题是你只能将它放在复合索引的前面(至少对于 mongo 3.2 是这样)

    所以我想为什么不创建自己的“地理空间”索引呢?我所需要的只是在 Lat, Lgn (X,Y) 上创建一个复合索引,并首先添加排序字段。然后我需要实现在框边界内搜索的逻辑,并专门指示 mongo 使用它(提示)。

    翻译成你的问题:

    db.collection.createIndex({ "relevance": 1, "loc_x": 1, "loc_y": 1 }, { "background": true } )
    

    逻辑:

    db.collection.find({
        "loc_x": { "$gt": -103, "$lt": -80.43 },
        "loc_y": { "$gt": 10.1, "$lt": 30.232 }
    }).hint("relevance_1_loc_x_1_loc_y_1") // or whatever name you gave it
    

    如果您需要包容性结果,请使用 $gte$lte

    并且您不需要使用 .sort(),因为它已经排序,或者如果需要,您可以对 relevance 进行反向排序。

    我遇到的唯一问题是盒子面积很小。寻找小区域比寻找大区域花费更多时间。这就是我为小区域搜索保留地理空间索引的原因。

    【讨论】:

      猜你喜欢
      • 2013-10-22
      • 2013-05-08
      • 2012-09-19
      • 1970-01-01
      • 2012-10-06
      • 2015-06-07
      • 2016-12-27
      • 1970-01-01
      • 2021-04-26
      相关资源
      最近更新 更多