父文档可能不存在时更新MongoDB子文档

时间:2015-05-17 21:06:43

标签: node.js mongodb subdocument

以下是我的数据,包含 books 集合,以及 books.reviews 子集合。

books = [{
    _id: ObjectId("5558f40ad498c653748cf045"),
    title: "Widget XYZ",
    isbn: 1234567890,
    reviews: [
        {
            _id: ObjectId("5558f40ad498c653748cf046"),
            userId: "01234",
            rating: 5,
            review: "Yay, this great!"
        },
        {
            _id: ObjectId("5558f40ad498c653748cf047"),
            userId: "56789",
            rating: 3,
            review: "Meh, this okay."
        }
    ]
}]

在Node.js中(使用Mongoose),我需要用户能够通过表单提交评论,将评论和书籍的isbn发送到服务器,具有以下条件:

  1. 如果该书已经不存在特定的isbn,请添加它,然后添加评论(显然它尚不存在)。
  2. 如果这本书确实存在......
    • 如果本书对该用户不存在评论,请添加。
    • 如果本书的评论确实存在此用户,请进行更新。
  3. 我可以通过4个单独的查询来完成此操作,但我知道这不是最佳的。我可以使用的查询数量最少(当然,这些查询是什么)?

1 个答案:

答案 0 :(得分:4)

你基本上有3个案例:

  1. 本书和评论都存在。这是一个简单的$set
  2. 这本书存在但不是评论。这需要$push
  3. 这本书不存在。这需要{upsert:1}$setOnInsert
  4. 在发生故障的情况下,我无法在不损害数据完整性的情况下找到统一其中任何两个的方法(请记住,MongoDB没有原子事务)。

    所以我的最好的想法如下:

    // Case 1:
    db.books.update({isbn:'1234567890',
                     review: { $elemMatch: {userID: '01234'}}},
                    {$set: {'review.$.rating': NEW_RATING}}
                   )
    
    // Case 2:
    db.books.update({isbn:'1234567890',
                     review: { $not: { $elemMatch: {userID: '01234'}}}},
                    {$push: {review: {rating: NEW_RATING, userID:'01234'}}}
                   )
    
    // Case 3:
    db.books.update({isbn:'1234567890'},
                    {$setOnInsert: {review: [{rating: NEW_RATING, userID:'01234'}]}},
                    {upsert:1}
                   )
    

    您可能会盲目地在原始版本中运行这三个更新,因为它们之间没有重叠的情况。所有这些操作都是idempotent。因此,您可以应用它们一次或多次,并始终获得相同的结果。这在故障转移的情况下尤其重要。此外,如果出现故障,您的数据库无法保持不一致或无法释放现有数据。在最坏的情况下,审核更新。最后这个应该保证数据的一致性,即使在并发更新的情况下(例如:在这种情况下,一个更新将覆盖另一个,但你最终不应该有两本文件用于同一本书或两篇评论同一本书的同一用户)。
    后面的一点必须得到证实,因为它已经很晚了,所以我的分析可能有点令人怀疑。

    最后需要注意的是,如果你想减少MongoDB和你的应用程序之间的往返次数,你可以看一下update database command,允许你在一个命令中包含多个更新。