5

这是我的数据,由一个书籍集合和一个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. 如果这本书真的存在...
    • 如果此用户的此书不存在评论,请添加评论。
    • 如果此用户的这本书的评论确实存在,请更新它。

我可以用 4 个单独的查询来做到这一点,但我知道这不是最优的。我可以使用的最少查询数量是多少(当然,这些查询是什么)?

4

1 回答 1

5

你基本上有3种情况:

  1. 这本书和评论都存在。这是一个简单的$set
  2. 这本书存在,但没有评论。这需要一个$push
  3. 这本书不存在。这种需要{upsert:1}$setOnInsert

在发生故障时,我无法找到一种方法来统一其中的任何两个而不损害数据完整性(请记住,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}
               )

您可能会盲目地以原始方式运行这三个更新,因为它们之间没有重叠的情况。事情的美妙之处在于所有这些操作都是幂等的。因此,您可以应用它们一次或多次,并始终获得相同的结果。这在故障转移的情况下尤其重要。此外,您的数据库无法在发生故障时出现不一致或丢失现有数据。在最坏的情况下,评论没有更新。最后,即使在并发更新的情况下,这也应该保证数据的一致性(即:在这种情况下,一个更新将覆盖另一个,但你不应该最终有同一本书的两个文档或同一用户的两个评论书)。
后一点必须得到证实,因为这里已经很晚了,所以我的分析可能有些怀疑。

最后一点,如果您想减少 MongoDB 和您的应用程序之间的往返次数,您可以查看允许您将多个更新包装在一个命令中的update数据库命令。

于 2015-05-18T21:23:27.977 回答