这是我的数据,由 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 发送到服务器,条件如下:
- 如果该书不存在特定的 isbn,请添加它,然后添加评论(它显然不存在)。
- 如果这本书确实存在……
- 如果此书此用户不存在评论,请添加。
- 如果此书的评论确实存在此用户,请更新它。
我可以使用 4 个单独的查询来做到这一点,但我知道这不是最优的。我可以使用的最少查询数量是多少(当然,这些查询是什么)?
最佳答案
你基本上有3种情况:
- 这本书和评论都存在。这是一个简单的
$set
- 该书存在,但评论不存在。这需要一个
$push
- 这本书不存在。这需要
{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}
)
您可能会盲目地以原始方式运行这三个更新,因为它们之间没有重叠的情况。事情的美妙之处在于所有这些操作都是 idempotent 。因此,您可以应用它们一次或多次,并始终获得相同的结果。这在故障转移的情况下尤其重要。此外,您的数据库无法在发生故障时出现不一致或丢失现有数据。在最坏的情况下,评论没有更新。最后,即使在并发更新的情况下,这应该保证数据一致性(即:在这种情况下,一个更新将覆盖另一个,但你不应该最终为同一本书或两个评论拥有两个文档同一本书的同一用户)。
后一点必须确认,因为这里已经晚了,所以我的分析可能有些怀疑。
最后一点,如果您想减少 MongoDB 和您的应用程序之间的往返次数,您可以看看 update
database command 允许您在一个命令中包装多个更新。
关于node.js - 当父文档可能不存在时更新 MongoDB 子文档,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30292159/