【问题标题】:MongoDB - Get the first matching document multiple times - Which is better?MongoDB - 多次获取第一个匹配的文档 - 哪个更好?
【发布时间】:2020-12-29 18:29:49
【问题描述】:

想象一个集合看起来像这样:

{
  created_at: Date,
  color: string, // 'blue' | 'red' | 'yellow'
}

我在一个集合中有数千或数百万个文档,这些文档具有随机 created_at 日期时间和不同颜色。 现在我想要最新创建的“蓝色”颜色文档和“红色”颜色最新创建的文档。

哪种方法最好? (请随意推荐另一个)

A) 多个并行查询

const latest = await Promise.all([
   collection.findOne({ color: 'blue' }, { sort: { created_at: -1 } }),
   collection.findOne({ color: 'red' }, { sort: { created_at: -1 } }),
]);

B) 一个聚合查询 - 组

const latest = await collection.aggregate([
  { $match: { color: { $in: ['blue', 'red'] } } },
  { $sort: { created_at: -1 } },
  {
    $group: {
      _id: "$color",
      latest: { $first: "$$ROOT" }
    }
  },
]).toArray();

C) 一个聚合查询 - 方面

const latest = await collection.aggregate([
  { $match: { color: { $in: ['blue', 'red'] } } },
  { $sort: { created_at: -1 } },
  {
    $facet: {
      blue: [
        { $match: { color: 'blue' } },
        { $limit: 1 },
      ],
      red: [
        { $match: { color: 'red' } },
        { $limit: 1 },
      ],
    }
  },
]).toArray();

哪一个最适合性能?如果我想为超过 2 种颜色执行此操作怎么办?

Sidequestion:我真的很想知道这对于 $facet 操作。由于 $facet 不能使用索引,在某些情况下并行执行多个查询似乎更好。如果每个组有很多文档(在本例中为“颜色”),使用索引似乎很有用。所以我猜选项C不是很好。对于选项B,我想知道MongoDB是否首先需要获取所有符合通用条件的文档,对它们进行排序,对它们进行分组,然后只取第一个......

提前致谢!

【问题讨论】:

    标签: mongodb performance mongodb-query aggregate


    【解决方案1】:

    您的解决方案都没有使用索引(我猜这是因为您还没有创建它)。

    创建以下索引:

    { color: 1, created_at: -1 }

    然后您可以对查询运行解释计划并查看它们的行为。

    我“认为”第一个最快。

    【讨论】:

    • 谢谢@Yahya。我当然会根据选择的方法创建所需的索引。对于 $facet 没关系,因为它永远不会使用任何索引。我可以创造这种情况并进行一些测试,是的,但我对直观的期望感到好奇。我的实际情况比较复杂,简化的测试可能不会得出正确的结论。额外问题:索引{ color: 1, created_at: -1 }{ created_at: -1, color: 1 } 会更好吗?
    • { color: 1, created_at: -1 } 在您的情况下更好,因为您将平等放在首位,然后是范围。 MongoDB 中的索引是 B+ 树。所以把平等放在首位是有道理的。
    • 嗨@Yahya,感谢您的帮助。我做了一些测试并将结果发布在答案中。
    【解决方案2】:

    似乎选项 A 是最好的,至少在我测试的情况下。

    我做了如下性能测试:

    我创建了一个包含 3.000.000 个文档的集合,其中包含 2 个字段:

    • created_at:2018-01-01T00:00:00.000Z 和 2030-01-01T00:00:00.000Z 之间的随机日期
    • color:随机字符串,31 种不同的选项

    我做了一个查询,以获取 17 种不同颜色中每种颜色的最新创建文档。

    结果

    No index { color: 1, created_at: -1 } { created_at: -1, color: 1 }
    A1) One FindOne (shell) 70 ms 60 ms 60 ms
    A2) FindOnes in parallel (JS) 21944 ms 543 ms 572 ms
    B) Aggregate - Group - First QueryExceededMemoryLimitNoDiskUseAllowed 9120 ms 10200 ms
    C) Aggregate - Facet QueryExceededMemoryLimitNoDiskUseAllowed 3860 ms 4770 ms

    选项 A1 是最快的,但这仅适用于一种颜色(我无法通过 shell 并行执行)。所以真正的赢家是选项A2!另请注意,A2 是通过连接到外部数据库的本地运行的 JS 脚本完成的。它仍然比直接通过 shell 完成的其他选项快得多。

    我检查了并行执行 5 个 findOne 而不是 17 个的影响:

    A3) 并行查找 (JS) | 6656 毫秒 | 334 毫秒 | 339 毫秒

    这明显更快。我怀疑如果您需要超过 17 个案例,其他选择可能会更好。它与我的需求无关,所以我没有对此进行测试。


    为了完整起见,确切的查询:

    A1)

    db.getCollection('colors').findOne({ color: 'Ivory' }, { sort: { created_at: -1 } })
    

    A2)

    const latest = await Promise.all(colors.map((c) => mongodb.collection('colors').findOne({ color: c }, { sort: { created_at: -1 } })));
    

    B)

    db.getCollection('colors').aggregate([
      { $match: { color: { $in: [
        'Ivory',
        'Teal',
        'Silver',
        'Purple',
        'Navy blue',
        'Pea green',
        'Gray',
        'Orange',
        'Maroon',
        'Charcoal',
        'Aquamarine',
        'Coral',
        'Fuchsia',
        'Wheat',
        'Lime',
        'Crimson',
        'Khaki'
      ] } } },
      { $sort: { created_at: -1 } },
      {
        $group: {
          _id: "$color",
          latest: { $first: "$$ROOT" }
        }
      },
    ]);
    

    C)

    db.getCollection('colors').aggregate([
      { $match: { color: { $in: [
        'Ivory',
        'Teal',
        'Silver',
        'Purple',
        'Navy blue',
        'Pea green',
        'Gray',
        'Orange',
        'Maroon',
        'Charcoal',
        'Aquamarine',
        'Coral',
        'Fuchsia',
        'Wheat',
        'Lime',
        'Crimson',
        'Khaki'
      ] } } },
      { $sort: { created_at: -1 } },
      {
        $facet: {
          Ivory: [
            { $match: { color: 'Ivory' } },
            { $limit: 1 },
          ],
          Teal: [
            { $match: { color: 'Teal' } },
            { $limit: 1 },
          ],
          Silver: [
            { $match: { color: 'Silver' } },
            { $limit: 1 },
          ],
          Purple: [
            { $match: { color: 'Purple' } },
            { $limit: 1 },
          ],
          Navyblue: [
            { $match: { color: 'Navy blue' } },
            { $limit: 1 },
          ],
          Peagreen: [
            { $match: { color: 'Pea green' } },
            { $limit: 1 },
          ],
          Gray: [
            { $match: { color: 'Gray' } },
            { $limit: 1 },
          ],
          Orange: [
            { $match: { color: 'Orange' } },
            { $limit: 1 },
          ],
          Maroon: [
            { $match: { color: 'Maroon' } },
            { $limit: 1 },
          ],
          Charcoal: [
            { $match: { color: 'Charcoal' } },
            { $limit: 1 },
          ],
          Aquamarine: [
            { $match: { color: 'Aquamarine' } },
            { $limit: 1 },
          ],
          Coral: [
            { $match: { color: 'Coral' } },
            { $limit: 1 },
          ],
          Fuchsia: [
            { $match: { color: 'Fuchsia' } },
            { $limit: 1 },
          ],
          Wheat: [
            { $match: { color: 'Wheat' } },
            { $limit: 1 },
          ],
          Lime: [
            { $match: { color: 'Lime' } },
            { $limit: 1 },
          ],
          Crimson: [
            { $match: { color: 'Crimson' } },
            { $limit: 1 },
          ],
          Khaki: [
            { $match: { color: 'Khaki' } },
            { $limit: 1 },
          ],
        }
      },
    ]);
    

    【讨论】:

      猜你喜欢
      • 2017-05-09
      • 2018-08-20
      • 1970-01-01
      • 1970-01-01
      • 2016-07-13
      • 2018-08-24
      • 2018-09-30
      • 2022-11-02
      • 1970-01-01
      相关资源
      最近更新 更多