假设我有一个名为“user_history”的集合。它包括一个名为“历史”的数组。 Collection 中还有一个“auto_increment”字段来模拟 MySQL 的字段。数组“history”中的每个元素都有一个名为“sequence”的字段,每次追加时都会将其设置为当前“auto_increment”。
我必须选择一个带有“userId”字段的文档,该字段是唯一的键。如果存在,我将在现有文档的“history”数组中添加一个新元素,并将“sequence”设置为当前“auto_increment”并将其增加 1。如果不存在,我将在“history”中插入一个元素的新文档' 数组的 'sequence' 0 并将 'auto_increment' 初始化为 1。这一切都必须以 ACID 方式运行。如果是 MySQL,我只会使用 SELECT FOR UPDATE。
这是“user_history”集合的示例
[
{
"user_id": 1,
"history": [
{
"sequence": 0,
"foo": "bar"
}
],
"auto_increment": 1,
}
]
是否只能通过MongoDB支持的功能,比如findAndModify来实现这个功能?
或者我忍不住用应用程序逻辑来处理它,例如使用乐观锁定和重试,或者完全不同的解决方案(例如分布式锁)?
db.collection.updateOne(
{user_id:1},
[
{ $replaceRoot: {
newRoot: { $mergeObjects: [
{ "user_id": 1, "history": [], "auto_increment": 0 },
"$$ROOT"
] }
} },
{ $set: {
history: { $concatArrays: [
"$history",
[{
"sequence": {$add: ["$auto_increment", 1]},
"foo": "bar"
}]
] },
auto_increment: {$add: ["$auto_increment", 1]}
} }
],{upsert: true})
{user_id:1}
是您的“选择更新”,[...]
是更新管道。
$replaceRoot
阶段实现“upsert”行为 - 如果没有匹配的文档,$$ROOT
将为空,并且“$mergeObject”中的第一个元素将用于替换文档。否则 $$ROOT
将覆盖第一个对象(假设所有字段都在那里),即文档将被自身替换。
$set
阶段追加history
数组并递增auto_increment
作为旁注,
auto_increment
字段的名称可能有点误导,因为它没有任何“自动” - 您需要手动增加它。另一方面,BSON 保证数组中项目的顺序,并且根据 sequence
的预期用途,您可能可以仅依赖于 history
数组中项目的索引。