MongoDB文档操作是原子的和隔离的,但它们是否一致?

dep*_*ner 17 concurrency consistency optimistic-locking mongodb

我正在将我的应用程序从App Engine数据存储区移植到MongoDB后端,并对"文档更新"的一致性提出疑问.我知道一个文档的更新都是原子的和隔离的,但有没有办法保证它们在不同的副本集中"一致"?

在我们的应用程序中,许多用户可以(并且将会)在一次更新期间通过向其中插入一些嵌入文档(对象)来同时尝试更新一个文档.我们必须确保这些更新发生在所有副本逻辑一致的方式,即当一个用户"把"几嵌入文档到主文件,其他用户可以把自己嵌入文档的父文档中,直到我们确保他们已经阅读并收到第一个用户的更新.

所以我的意思是一致的是,我们需要一种方法来确保如果两个用户试图在一个文档进行更新恰好在同一时间,MongoDB中只允许这些更新要经过的一个,并丢弃另一个(或至少可以防止两者发生.我们不能在这里使用标准的"分片"解决方案,因为单个更新不仅仅包含增量或减量.

保证一个特定文档一致性的最佳方法是什么?

dcr*_*sta 19

可能还有其他方法可以实现此目的,但一种方法是对文档进行版本控制,并仅针对用户之前读过的版本发布更新(即,确保自上次读取文档以来没有其他人更新过该文档).以下是使用pymongo的这种技术的简短示例:

>>> db.foo.save({'_id': 'a', 'version': 1, 'things': []}, safe=True)
'a'
>>> db.foo.update({'_id': 'a', 'version': 1}, {'$push': {'things': 'thing1'}, '$inc': {'version': 1}}, safe=True)
{'updatedExisting': True, 'connectionId': 112, 'ok': 1.0, 'err': None, 'n': 1}
Run Code Online (Sandbox Code Playgroud)

请注意,在上面,键"n"为1,表示文档已更新

>>> db.foo.update({'_id': 'a', 'version': 1}, {'$push': {'things': 'thing2'}, '$inc': {'version': 1}}, safe=True)
{'updatedExisting': False, 'connectionId': 112, 'ok': 1.0, 'err': None, 'n': 0}
Run Code Online (Sandbox Code Playgroud)

在这里我们尝试更新错误的版本,键"n"为0

>>> db.foo.update({'_id': 'a', 'version': 2}, {'$push': {'things': 'thing2'}, '$inc': {'version': 1}}, safe=True)
{'updatedExisting': True, 'connectionId': 112, 'ok': 1.0, 'err': None, 'n': 1}
>>> db.foo.find_one()
{'things': ['thing1', 'thing2'], '_id': 'a', 'version': 3}
Run Code Online (Sandbox Code Playgroud)

请注意,此技术依赖于使用安全写入,否则我们不会收到指示已更新文档数量的确认.对此的变体将使用findAndModify命令,该命令将返回文档,或者None(如果没有找到与查询匹配的文档)(在Python中).findAndModify允许您返回新的(即应用更新后)或旧版本的文档.


mne*_*syn 3

MongoDB 不提供主主复制或多版本并发。换句话说,写入始终发送到副本集中的同一台服务器。默认情况下,甚至禁用从辅助服务器的读取,因此默认行为是您一次仅与一台服务器通信。因此,如果使用原子修饰符(如 等),则无需担心安全模式下结果不一致$inc, $push

如果您不想将自己限制在这些原子修饰符上,那么按照 dcrosta (和mongo 文档)的建议进行比较和交换看起来是个好主意。然而,所有这些都与副本集或分片无关 -在单服务器场景中是相同的

如果您还需要在数据库/节点发生故障时确保读取一致性,则应确保在安全模式下写入大多数服务器。

如果允许不安全读取,这两种方法的行为会有所不同:原子更新操作仍然有效(但可能会产生意外结果),而比较和交换方法将会失败。