【问题标题】:MongoDB query joining two collectionsMongoDB 查询加入两个集合
【发布时间】:2016-06-07 23:44:40
【问题描述】:

我有 2 个 mongo 集合:

companies:每条记录都是一个包含多个字段(城市、国家等)的公司 —> 100k rows

{company_id:1, country:"USA", city:"New York",...}
{company_id:2, country:"Spain", city:"Valencia",... }
{company_id:3, country:"France", city:"Paris",... }

scores:有日期块,每个块都有company_id + score,例子——>100k rows in each block

{date: 2016-05-29, company_id:1, score:90}
{date: 2016-05-29, company_id:2, score:87}
{date: 2016-05-29, company_id:3, score:75}
...
{date: 2016-05-22, company_id:1, score:88}
{date: 2016-05-22, company_id:2, score:87}
{date: 2016-05-22, company_id:3, score:76}
...
{date: 2016-05-15, company_id:1, score:91}
{date: 2016-05-15, company_id:2, score:82}
{date: 2016-05-15, company_id:3, score:73}
...

目标

我想检索可以按某些字段(国家、城市、...)过滤的公司列表+其最新分数(2016-05-29),ordered by score descending

即:在一个集合中过滤,在另一个集合中过滤+排序

注意:scores.date 上有一个索引,我们可以轻松快速地定位/预计算最高日期(本例中为 2016-05-29)

尝试

我一直在尝试使用$lookup 进行aggregate 查询。当过滤器完成(并且公司数量较少)时,查询速度会更快。

查询如下:-

db.companies.aggregate([
{$match: {"status": "running", "country": "USA", "city": "San Francisco",
         "categories": { $in: ["Software"]}, dummy: false}},
{$lookup: {from: "scores", localField: "company_id", foreignField: "company_id", as:"scores"}},
{$unwind: "$scores"},
{$project: {_id:            "$_id",
            "company_id":   "$company_id",
            "company_name": "$company_name",
            "status":       "$status",
            "city":         "$city",
            "country":      "$country",
            "categories":   "$categories",
            "dummy":        "$dummy",
            "score":        "$scores.score",
            "date":         "$scores.date"}},
{$match: {"date" : ISODate("2016-05-29T00:00:00Z")}},
{$sort: {"score":-1}}
],{allowDiskUse: true})

但当过滤器很小或为空(更多公司)时,$sort 部分需要几秒钟。

db.companies.aggregate([
{$match: {"status": "running"}},
{$lookup: {from: "scores", localField: "company_id", foreignField: "company_id", as:"scores"}},
{$unwind: "$scores"},
{$project: {_id:            "$_id",
            "company_id":   "$company_id",
            "company_name": "$company_name",
            "status":       "$status",
            "city":         "$city",
            "country":      "$country",
            "categories":   "$categories",
            "dummy":        "$dummy",
            "score":        "$scores.score",
            "date":         "$scores.date"}},
{$match: {"date" : ISODate("2016-05-29T00:00:00Z")}},
{$sort: {"score":-1}}
],{allowDiskUse: true})

可能是因为过滤器找到的公司数量。 59 行比 89k 更容易订购

> db.companies.count({"status": "running", "country": "USA", "city": "San Francisco", "categories": { $in: ["Software"]}, dummy: false})
59
> db.companies.count({"status": "running"})
89043

我尝试了不同的方法,按分数聚合,按日期过滤,按分数排序(索引日期+分数在这里非常有用),一切都非常快,直到我过滤公司的最后一个$match属性

db.scores.aggregate([
{$match:{"date" : ISODate("2016-05-29T00:00:00Z")}},
{$sort:{"score":-1}},
{$lookup:{from: "companies", localField: "company_id", foreignField: "company_id", as:"companies"}},
{$unwind:"$companies"},
{$project: {_id:             "$companies._id",
            "company_id":    "$companies.company_id",
            "company_name":  "$companies.company_name",
            "status":        "$companies.status",
            "city":          "$companies.city",
            "country":       "$companies.country",
            "categories":    "$companies.categories",
            "dummy":         "$companies.dummy"}},
            "score":         "$score",
            "date":          "$date"
{$match:{"status": "running", "country":"USA", "city": "San Francisco",
         "categories": { $in: ["Software"]}, dummy: false}}
],{allowDiskUse: true})

使用这种方法,大过滤器(前面的例子)很慢,小过滤器(只是{"status": "running"})更快

有什么方法可以加入这两个集合,过滤它们并按一个字段排序?

【问题讨论】:

标签: mongodb mongodb-query aggregation-framework


【解决方案1】:

如我所见,每个公司在不同日期只有 几个 分数(不多)。所以这是一种 1:few 关系。

所以我首先想到的是:为什么不将分数放入公司数据库中?

{ company_id:1, 
  country:"USA", 
  city:"New York",
  ...
  scores: [
    {date: 2016-05-29, score:90},
    ...
  ]
}

这样结构更符合您的访问模式,您可以完全跳过查找部分。这意味着,您可以定义一个适当的索引并使用find() 而不是聚合。

除此之外,我想知道,为什么要使用 allowDiskUse:true 标志,100k 个文档听起来并不多,它们应该完全适合内存,甚至可以放入有限的 (128M) 聚合管道缓冲区。

解释一下,为什么过滤器(短 = 不太选择性,长 = 非常选择性)表现不同,具体取决于您开始的收集(分数与公司)

  • 公司优先:
    • short 过滤器:很多公司都符合条件,因此必须对很多公司进行排序(您需要将它们全部放在内存中进行排序)。如果部分结果集被写入磁盘,这可能需要相当长的时间。
    • long过滤器:只有一小部分公司匹配,最后只有少数公司需要排序,可能完全在内存中
  • 得分第一 - 日期可能会产生影响,因为它定义了有多少公司受到影响
    • long 过滤到底:必须搜索前面聚合步骤的结果,才能找到匹配的元素。没有索引可以用于此。因此,匹配操作可能需要更长的时间,因为需要评估更多标准 - 可能是针对磁盘上的数据。
    • short 最后过滤:前面阶段的结果只需要搜索一次。

那么你应该检查什么:

  • 禁用allowDiskUse,检查查询是否仍然适合内存或检查tmp文件,数据是否实际写入磁盘
  • 限制搜索范围,减少要处理的数据量
  • 更改架构以更好地匹配您的访问模式

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-09-27
    • 2021-11-22
    • 2020-05-12
    • 2018-12-09
    • 2012-05-11
    • 1970-01-01
    • 2023-03-28
    • 2020-03-24
    相关资源
    最近更新 更多