原子性和事务
在此页面上
在MongoDB中,写入操作在单个文档级别上具有 原子性,即使该操作修改多个值也是如此。当多个更新命令并行发生时,每个单独的命令确保查询条件仍然匹配。
为保证并发更新命令不会相互冲突,可以在更新过滤中指定字段的预期当前值。
例子
考虑包含此文档的集合:
db.games.insertOne( { _id: 1, score: 80 } )
这些更新操作会同时发生:
// Update A db.games.updateOne( { score: 80 }, { $set: { score: 90 } } ) // Update B db.games.updateOne( { score: 80 }, { $set: { score: 100 } } )
一个更新操作会将文档的 score
字段设置为 90
或 100
。在此更新完成后,第二个更新操作不再与查询谓词 { score: 80 }
匹配,因此不会执行。
警告
对于并发更新操作,在未更新的字段上指定过滤可能会导致意外结果。示例,考虑这些更新操作是否同时发生:
// Update A db.games.updateOne( { _id: 1 }, { $set: { score: 90 } } ) // Update B db.games.updateOne( { _id: 1 }, { $set: { score: 100 } } )
一个更新操作完成后,其余操作仍与查询谓词 { _id: 1 }
匹配。因此,两个更新操作都会发生,并且存储的 score
值会反映第二个更新操作。这是有问题的,因为发出第一次更新的客户端不会收到任何表明更新已被覆盖的指示,并且 score
值与预期不同。
当更新过滤位于与正在更新的字段不同的字段时,为防止写入操作冲突,请使用$inc
操作符。
示例,考虑这些更新操作是否同时发生:
// Update A db.games.updateOne( { _id: 1 }, { $inc: { score: 10 } } ) // Update B db.games.updateOne( { _id: 1 }, { $inc: { score: 20 } } )
一个更新操作完成后,其余操作仍与查询谓词 { _id: 1 }
匹配。但是,由于这些操作会修改 score
的当前值,因此它们不会相互覆盖。这两个更新都会得到反映,生成的 score
为 110
。
提示
Store Unique Values
多文档事务
当单个写操作(例如 db.collection.updateMany()
)修改了多份文档,则每份文档的修改都是原子性的,但整个操作不是原子性的。
在执行多文档写入操作时,无论是通过单次写入操作还是多次写入操作,其他操作都可能会交错进行。
对于需要对多个文档(在单个或多个集合中)原子性读取和写入的情况,MongoDB 支持分布式事务,包括副本集和分片集群上的事务。
有关详细信息,请参阅事务。
重要
在大多数情况下,与单文档写入操作相比,分布式事务会产生更高的性能成本,并且分布式事务的可用性不应取代有效的模式设计。在许多情况下,非规范化数据模型(嵌入式文档和数组)仍然是数据和使用案例的最佳选择。换言之,对于许多场景,适当的数据建模将最大限度地减少对分布式事务的需求。
有关其他事务使用注意事项(如运行时间限制和 oplog 大小限制),另请参阅生产注意事项。