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

时间:2011-11-10 15:07:27

标签: mongodb concurrency optimistic-locking consistency

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

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

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

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

2 个答案:

答案 0 :(得分: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}

在上面注意,键“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允许您返回新的(即应用更新后)或旧版本的文档。

答案 1 :(得分:3)

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

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

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

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