MongoDB Aggregation - $unwind order 文档是不是与嵌套数组 order 相同

Posted

技术标签:

【中文标题】MongoDB Aggregation - $unwind order 文档是不是与嵌套数组 order 相同【英文标题】: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 聚合框架来:

    按_id对文档进行排序 在values 属性上使用$unwind 来解构文档 使用 $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" 

【问题讨论】:

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

以上是关于MongoDB Aggregation - $unwind order 文档是不是与嵌套数组 order 相同的主要内容,如果未能解决你的问题,请参考以下文章

MongoDB 聚合管道(Aggregation Pipeline)

mongodb Aggregation聚合操作之$bucket

mongodb Aggregation聚合操作之$facet

mongoDB: Aggregation - 是不是有相当于原生 node.js 驱动程序的 $lookup 连接?

MongoDB $reduce(aggregation) 组与数组中嵌套文档的总和并按组计数

Mongodb aggregation 基本操作示例