想象一下,有一个文档包含一个字段:{availableSpots:100}
有数百万的用户,他们通过向API服务器发送请求来争夺位置。
每次请求到来时,服务器都会读取文档,如果availableSpot> 0,则会将其递减1,并在另一个集合中创建预订。
现在我读到mongodb每当执行更新操作时都会锁定文档。
如果同时存在一百万个请求,将会发生什么?同一文档会不断被锁定会花费很长时间吗?另外,服务器在尝试更新文档之前会读取文档的值,并且在获取锁之前,该点可能不再可用。如何最好地处理呢?
答案 0 :(得分:0)
MongoDB使用Wired Tiger作为默认存储引擎,从3.2版开始。
有线老虎提供document level concurrency:
来自文档:
WiredTiger使用文档级并发控制进行写入 操作。结果,多个客户端可以修改不同的 集合中的文档。
对于大多数读写操作,WiredTiger使用乐观 并发控制。 WiredTiger仅在全局使用意图锁, 数据库和收集级别。当存储引擎检测到 两个操作之间发生冲突,一个操作将引发写冲突 导致MongoDB透明地重试该操作。
当多个客户端试图更新文档中的值时,将仅锁定该文档,而不是整个集合。
答案 1 :(得分:0)
最重要的是原子性和并发性。
1。原子性
如果可用,您的操作将进行更新(减1)Spots> 0:
db.collection.updateOne({"availableSpots" :{$gt : 0}}, { $inc: { availableSpots: -1 })
是原子的。
$ inc是单个文档中的原子操作。
引用:https://docs.mongodb.com/manual/reference/operator/update/inc/
2。并发 由于MongoDB具有用于写操作的文档级并发控制。每次更新都将锁定文档。
现在您的问题:
如果同时存在一百万个请求,将会发生什么?
是的,由于锁定,每次更新都会一次执行,因此会减慢速度。
服务器在尝试更新文档之前先读取文档的值 文档,并且在获取锁之前,该位置可能不是 可用了。
由于该操作是原子操作,因此不会发生。它会根据您的需要工作,仅执行100次更新,受影响的行数大于0或等于1。
答案 2 :(得分:0)
我的理解是,您担心许多针对两个独立集合的并发ACID兼容事务的性能:
spots
)bookings
),其中包含多个文件,每个预订一个。
现在我读到mongodb每当执行更新操作时就会锁定文档。也有可能线程正在获取“ availableSpot> 0” 在同一时间是正确的,但实际上是availableSpot 可能不足以满足所有要求。该如何处理?
在版本4.0中,MongoDB提供了对副本集执行多文档事务的功能。 (即将发布的MongoDB 4.2将把这种多文档ACID事务功能扩展到分片集群。)
这意味着在事务提交之前,在多文档事务中看不到写操作(例如,按照您建议的方法对spots
和bookings
集合进行更新)。
尽管如此,如MongoDB documentation on transactions中所述,非规范化方法通常比多文档事务提供更好的性能:
在大多数情况下,多文档交易可带来更高的性能 单笔文档写入的成本以及 多文档交易不应替代有效 模式设计。在许多情况下,非规范化数据模型 (嵌入的文档和数组)将继续是您的最佳选择 数据和用例。也就是说,在许多情况下,对数据建模 适当地减少了多文档交易的需要。
在MongoDB中,对单个文档的操作是 atomic 。因为您可以使用嵌入式文档和数组来捕获单个文档结构中的数据之间的关系,而不是在多个文档和集合之间进行规范化,所以这种单文档原子性避免了在许多实际用例中进行多文档事务的需要。
但是请记住,如果您的用例在一个集合中以包含一个availableSpots子文档和数千个bookings
子文档的单个非规范化文档的形式实现,则作为最大文档可能不可行。大小为16MB。
因此,总而言之,非原子化写原子性方法通常会比多文档方法更好,但受到最大16MB文件大小的限制。
答案 3 :(得分:0)
您可以在尝试更新文档时尝试使用findAndModify()
选项。每次您都需要挑选该文档中要更新的任何字段。另外,由于mongo db将数据复制到主节点和辅助节点,因此您可能还需要调整WriteConcern
的值。您可以在官方文档中阅读有关此内容的更多信息。我有一些类似的代码,可以使用spring mongoDB
处理mongoTemplate
中类似的并发问题。让我知道是否需要与此相关的Java。