【问题标题】:MongoDB Aggregation - Does $unwind order documents the same way as the nested array orderMongoDB Aggregation - $unwind order 文档是否与嵌套数组 order 相同
【发布时间】:2020-11-17 15:03:18
【问题描述】:

我正在徘徊是否在聚合管道中为嵌套数组的文档使用 $unwind 运算符是否会以与数组中项目的顺序相同的顺序返回解构的文档。 例子: 假设我有以下文件

{ "_id" : 1, "item" : "foo", values: [ "foo", "foo2", "foo3"] }
{ "_id" : 2, "item" : "bar", values: [ "bar", "bar2", "bar3"] }
{ "_id" : 3, "item" : "baz", values: [ "baz", "baz2", "baz3"] }

我想在我的应用程序代码中对所有文档中的所有值使用分页。所以,我的想法是使用 mongo 聚合框架来:

  1. 按_id对文档进行排序
  2. values 属性上使用$unwind 来解构文档
  3. 使用 $skip 和 $limit 来模拟分页

所以使用上述示例的问题是:

是否保证以下聚合管道:

[
    {$sort: {"_id": 1}},
    {$unwind: "$values"}
]

将始终导致以下文档具有完全相同的顺序?:

{ "_id" : 1, "item" : "foo", values: "foo" }
{ "_id" : 1, "item" : "foo", values: "foo2" }
{ "_id" : 1, "item" : "foo", values: "foo3" }
{ "_id" : 2, "item" : "bar", values: "bar" }
{ "_id" : 2, "item" : "bar", values: "bar2" }
{ "_id" : 2, "item" : "bar", values: "bar3" }
{ "_id" : 3, "item" : "baz", values: "baz" }
{ "_id" : 3, "item" : "baz", values: "baz2" }
{ "_id" : 3, "item" : "baz", values: "baz3" }

【问题讨论】:

  • 我不能依赖这个在线示例。在现实生活中,我将在数组中有更多的文档和项目(可能是数百万)。我正在寻找官方答案。就像简单的find({})不能保证文档的顺序一样,但是用一小部分文档测试它返回的顺序是一样的
  • 这没有明确记录,如果您为 Atlas 或 MongoDB Enterprise 付费,我建议您通过官方支持渠道。
  • MongoDB 源代码调用 libunwind 函数迭代链中的帧。由于此类函数读取磁盘上的二进制文件,因此无法更改顺序、跳过、交换帧...
  • @Valijon。谢谢你的回答。我不知道wiredTiger 引擎实际上是如何将文档存储在磁盘上的。但它看起来合乎逻辑,因为数组顺序得到保证,被破坏的文档的顺序总是相同的。甚至,官方文档中的示例都是有序的:),但如果我们可以依赖该顺序,则没有提及。

标签: mongodb mongodb-query aggregation-framework


【解决方案1】:

我也在MongoDB community forum 中问过同样的问题。一个证实我假设的答案是从 MongoDB 的成员发布的。

简单地说:

是的,上面示例中返回文档的顺序将始终相同。它遵循数组字段的顺序。

【讨论】:

    【解决方案2】:

    如果您确实遇到订单问题。您可以使用includeArrayIndex 来保证订单。

    [
     {$unwind: {
       path: 'values',
       includeArrayIndex: 'arrayIndex'
     }},
     {$sort: {
       _id: 1,
       arrayIndex: 1
     }},
     { $project: {
        index: 0
     }}
    ]
    

    【讨论】:

    • 我认为它不会有效率。您在 $unwind 阶段之后对文档进行排序。这对性能有害,并且可能超过 100MB RAM 的限制。我的问题不是“如何实现这些结果?”但是“我的管道的输出总是一样吗?”
    • 这无法回答是否会曾经“遇到订单问题”,这就是问题所在。如果可能,我们希望避免使用$sort
    【解决方案3】:

    根据我在https://github.com/mongodb/mongo/blob/0cee67ce6909ca653462d4609e47edcc4ac5c1a9/src/mongo/db/pipeline/document_source_unwind.cpp 看到的情况

    游标迭代器使用 getNext() 方法展开数组:

    DocumentSource::GetNextResult DocumentSourceUnwind::doGetNext() {
        auto nextOut = _unwinder->getNext();
        while (nextOut.isEOF()) {
            .....
            // Try to extract an output document from the new input document.
            _unwinder->resetDocument(nextInput.releaseDocument());
            nextOut = _unwinder->getNext();
        }
    
        return nextOut;
    }
    

    getNext() 的实现依赖于数组的索引:

    DocumentSource::GetNextResult DocumentSourceUnwind::Unwinder::getNext() {
    
                ....
                // Set field to be the next element in the array. If needed, this will automatically
                // clone all the documents along the field path so that the end values are not shared
                // across documents that have come out of this pipeline operator. This is a partial deep
                // clone. Because the value at the end will be replaced, everything along the path
                // leading to that will be replaced in order not to share that change with any other
                // clones (or the original).
                _output.setNestedField(_unwindPathFieldIndexes, _inputArray[_index]);
                indexForOutput = _index;
                _index++;
                _haveNext = _index < length;
    
                .....
        return _haveNext ? _output.peek() : _output.freeze();
    }
    

    因此,除非上游有任何与文档顺序混淆的内容,否则光标应该以与子文档存储在数组中的顺序相同的顺序展开文档。

    我不记得合并对于分片集合是如何工作的,我想可能会有这样的情况,即来自其他分片的文档从 2 个连续展开的文档之间返回。代码的 sn-p 保证的是,在展开包含数组中上一项的文档之前,永远不会返回包含数组中下一项的展开文档。

    顺便说一句,在一个数组中包含数百万个项目是一个非常极端的设计。即使是数组中 20 字节的项目,也会超过 16Mb 的文档限制。

    【讨论】:

    • 非常感谢您为回答这个问题所做的努力。你说的对。几个小时前,MongoDB Stuff 的一名成员在我提出这个问题的其他沟通渠道中证实了这一点。如果可能的话,我会添加一个带有指向它的链接的答案。
    • 很高兴您解决了您的问题。 member of staff 是聚合框架之母 =) 你几乎找不到更可靠的来源。
    猜你喜欢
    • 1970-01-01
    • 2020-07-01
    • 2020-01-13
    • 1970-01-01
    • 2017-08-27
    • 2012-09-27
    • 2019-05-20
    • 1970-01-01
    • 2020-11-26
    相关资源
    最近更新 更多