17

我正在将我的应用程序从 App Engine 数据存储区移植到 MongoDB 后端,并且对“文档更新”的一致性有疑问。我知道一个文档上的更新都是原子的和孤立的,但是有没有办法保证它们在不同的副本集之间是“一致的”?

在我们的应用程序中,许多用户可以(并且将会)尝试通过在一次更新期间向其中插入一些嵌入式文档(对象)来同时更新一个文档。我们需要确保这些更新在所有副本中以逻辑一致的方式发生,即当一个用户将一些嵌入文档“放入”父文档中时,其他用户不能将他们的嵌入文档放入父文档中,直到我们确保他们已经阅读并收到第一个用户的更新。

所以我所说的一致性是我们需要一种方法来确保如果两个用户试图同时对一个文档执行更新, MongoDB只允许其中一个更新通过,而丢弃另一个(或至少防止两者发生)。我们不能在这里使用标准的“分片”解决方案,因为单个更新不仅仅包含增量或减量。

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

4

2 回答 2

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}

注意上面,key“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}

在这里,我们尝试针对错误的版本进行更新,键“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}

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

于 2011-11-10T15:34:46.857 回答
3

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

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

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

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

于 2011-11-11T12:59:32.763 回答