【问题标题】:MongoDB - most efficient way of getting the last version of a documentMongoDB - 获取文档最新版本的最有效方式
【发布时间】:2012-02-22 14:56:26
【问题描述】:

我正在使用 MongoDB 来保存文档集合。

每个文档都有一个 _id(版本),它是一个 ObjectId。每个文档都有一个在不同版本之间共享的 documentId。这也是创建第一个文档时分配的 OjectId。

在给定 documentId 的情况下,查找文档最新版本的最有效方法是什么?

即我想获取 _id = max(_id) 和 documentId = x 的记录

我需要使用 MapReduce 吗?

提前致谢,

山姆

【问题讨论】:

    标签: mongodb nosql


    【解决方案1】:

    我输入的内容与 Daimon 的第一个答案相同,使用的是 sortlimit。由于 _id 的生成方式,可能不建议这样做,尤其是对于某些驱动程序(它们使用随机数而不是最不重要部分的增量)。它具有第二个 [与更小的东西,如毫秒相反] 分辨率作为最重要的部分,但最后一个数字可以是随机数。因此,如果您让用户在一秒钟内保存两次(可能不太可能,但值得注意),您最终可能会得到一个稍微乱序的最新文档。

    有关 ObjectID 结构的更多详细信息,请参阅http://www.mongodb.org/display/DOCS/Object+IDs#ObjectIDs-BSONObjectIDSpecification

    我建议在您的文档中添加一个明确的 versionNumber 字段,以便您可以使用该字段以类似的方式进行查询,如下所示:

    db.coll.find({documentId: <id>}).sort({versionNum: -1}).limit(1);
    

    编辑以回答 cmets 中的问题

    您可以将常规日期时间直接存储在 MongoDB 中,但它只会以“日期时间”格式将毫秒精度存储在 MongoDB 中。如果这足够好,那就更简单了。

    BsonDocument doc = new BsonDocument("dt", DateTime.UtcNow);
    coll.Insert (doc);
    doc = coll.FindOne();
    // see it doesn't have precision...
    Console.WriteLine(doc.GetValue("dt").AsUniversalTime.Ticks);
    

    如果你想要 .NET DateTime (ticks)/Timestamp 精度,你可以做一堆强制转换来让它工作,比如:

    BsonDocument doc = new BsonDocument("dt", new BsonTimestamp(DateTime.UtcNow.Ticks));
    coll.Insert (doc);
    doc = coll.FindOne();
    // see it does have precision
    Console.WriteLine(new DateTime(doc.GetValue("dt").AsBsonTimestamp.Value).Ticks);
    

    再次更新!

    看起来 BsonTimestamp 的真正用途是在第二个分辨率内生成唯一的时间戳。所以,你真的不应该像我在最后几行代码中那样滥用它们,它实际上可能会搞砸结果的顺序。如果您需要以 Tick(100 纳秒)分辨率存储 DateTime,您可能应该只存储 64 位 int “ticks”,这将在 mongodb 中排序,然后在将其拉出后将其包装在 DateTime 中再次访问数据库,如下所示:

    BsonDocument doc = new BsonDocument("dt", DateTime.UtcNow.Ticks);
    coll.Insert (doc);
    doc = coll.FindOne();
    DateTime dt = new DateTime(doc.GetValue("dt").AsInt64);
    // see it does have precision  
    Console.WriteLine(dt.Ticks);
    

    【讨论】:

    • 谢谢 wes,您的意思是版本的整数?
    • 使用整数计数器是可能的,但它根本不可扩展......使用高分辨率时间戳会是更好的方法 - 两个文档总是有可能共享相同的时间戳 - 但如果它是任务 -在您的情况下至关重要,也许使用 RDBMS 是更好的方法?
    • 好的。说得通。这不是关键任务,但显然需要工作。很难将 RDBMS 概念抛在脑后。感谢您的意见(两者)
    • @wes +daimon - 使用 .NET 驱动程序,我声明了一个 DateTime,但在表示为 BsonTimestamp 时遇到了问题。你们中的任何一个都有使用 .NET 驱动程序的经验吗,我最好如何拥有 Timestamp 属性。谢谢。
    • @WesFreeman - 谢谢!很好的例子。我确实看过使用带有 DateTime/ticks 的 BsonDocument,如此处所述alexmg.com/post/2011/09/30/… 这与您描述的方式相似,尽管仅使用刻度更有效。回到您最初使用 versionNumber 的建议,因此最好使用此处描述的序列mongodb.org/display/DOCS/Object+IDs#ObjectIDs-SequenceNumbers - 它可能是长的(有符号的 64 位),甚至按实体排序以提供更高的分辨率。对此有什么想法吗?
    【解决方案2】:

    添加包含两个字段(documentId,_id)的索引并且不使用max(用于什么)?使用带有 documentId = x 的查询,按 _id 和 limit(1) 结果排序 DESC 以获得最新的。记住正确的索引排序顺序(也包括 DESC)

    类似的东西

    db.collection.find({documentId : "x"}).sort({_id : -1}).limit(1)
    

    其他方法(更非规范化)是使用其他集合与文档,例如:

    {
        documentId : "x",
        latestVersionId : ...
    }
    

    使用原子操作可以安全地更新这个集合。添加适当的索引将使查询变得快如闪电。

    需要考虑一件事 - 我不确定是否始终可以安全地使用 ObjectID 来订购最新版本。使用时间戳可能是更确定的方法。

    【讨论】:

    • 完美,感谢 Daimon。只是关于第二个选项的一个问题,如果新记录被插入到主文档集合中,你如何原子地插入和更新非规范化的“索引”集合?是否可以在更新第一个文档的索引记录之前插入另一个文档实例?那有意义吗?您可以执行 findAndModify 权限以确保文档没有更改?我猜它是否获得了最新的 _id 并再试一次?这是要点吗?再次感谢。
    • MongoDB 不支持 RDBMS 之类的触发器,因此您必须在应用程序端进行。您可以使用 findAndModify 查找 id/timestamp 较低的文档,然后更新并更新它。因为 findAndModify 是原子操作,它只会在新值确实比当前值更年轻时更新文档 - 这样您就不必担心并发更新。
    • 再次感谢。假设我使用版本号或时间戳,如果另一个进程添加了另一个文档实例并更新了“索引”文档,则 findAndModfy 将失败。 findAndModify 将尝试更新“索引”文档,其中版本现在比当前更新更新(更大)。如果发生这种情况,我是否应该简单地获取最新版本并尝试再次更新索引文档?对不起,如果我重复了什么,只是想清楚一点
    • 这是针对单个结果的。一系列文件是否有可比性? .find({documentId: {$in: [ ... ]}})
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-07-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-04-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多