zodb:数据库冲突失败

时间:2012-03-21 17:52:05

标签: python database multithreading conflict zodb

我有一台服务器和一台客户端。

客户端发送请求。该请求具有与其相关联的特定密钥,例如, a-1a-2b-1b-4

如果同时有两个相同密钥的请求进入,则会出现冲突错误,因为正在修改相同的数据结构。

我可以调整客户端,不要一次发送两个相同密钥的请求。但是,我希望这个系统能够与多个客户端一起使用。让客户端协调他们发送给服务器的内容似乎很愚蠢。相反,如果该密钥已被修改,我希望服务器只是阻止某个密钥的请求,直到具有相同密钥的其他请求完成为止。

为此,我创建了一个锁定系统。在服务器上的函数开头,我做:

key = ...
print "Acquiring %s lock..." % (key,)
KEY_LOCKS[key].acquire()
print "%s lock acquired." % (key,)
def after_commit_hook(success):
    KEY_LOCKS[key].release()
    print "(after %s commit): Released %s lock" % (('failed', 'successful')[success], key)
transaction.get().addAfterCommitHook(after_commit_hook)

其中KEY_LOCKS是一个dict映射到threading.Lock的键。然后遵循修改持久数据结构的代码。

我认为会发生这样的情况:如果请求进入已经处理的密钥,则在获取锁时会阻塞。只有当先前的请求已经提交(因此超出任何冲突错误)时,新请求才会恢复。在获得锁定之前,请求不会发生任何冲突。

大多数请求都可以正常运行:

Acquiring a-b lock...
a-b lock acquired.
(after successful commit): Released a-b lock
Acquiring a-c lock...
a-c lock acquired.
(after successful commit): Released a-c lock

但是,当发送相同的密钥时,仍然存在问题,即使锁定似乎有效:

Acquiring q-q lock...
q-q lock acquired.
Acquiring q-q lock...
(after successful commit): Released q-q lock
q-q lock acquired.
(after failed commit): Released q-q lock
repoze.retry retrying, count = 1
Traceback (most recent call last):
...
ConflictError: database conflict error (oid 0x13009b, class persistent.list.PersistentList)

然后请求重试。请注意,q-q lock仅在成功提交后获取。

是什么给出的?为什么这个系统不能防止冲突错误?我的假设在哪里不正确?


编辑:好吧,如果在transaction.get().addAfterCommitHook(after_commit_hook)行之前我放transaction.begin(),那就行了。对于我的生活,我无法弄清楚为什么。在transaction.begin()行之前,我的整个代码是:

post = request.params
if not post: return Response("No data!")

data = eval(post['data'])
time_parsed = time.time() 
my_app = request.context

这解决了我的问题,但我不是把它作为一个答案,因为我仍然想知道:如果我之前没有开始新的交易,为什么会出现冲突错误?

1 个答案:

答案 0 :(得分:3)

ZODB从事务开始的那一刻起提供具有一致视图的连接。这意味着,在新事务开始之前,线程不会看到其他线程对数据库所做的更改!这是名为Multiversion Concurrency Control的数据库的基本功能。

换句话说,如果您希望应用程序线程不会因使用锁定而发生冲突,则还需要在锁定可用时启动新事务。

在我看来,锁定是您想要避免的性能瓶颈。您持有锁的时间越长,遇到不一致数据的可能性就越大(您的起始状态与数据库状态相比)。

在Zope中,使用了一种乐观的方法:当引发冲突错误时,中止事务,并从一开始就重新尝试重新执行。如果您的事务很短,快速事务,则可以避免大多数锁定问题,而只需在持久性更改发生更改时重新计算对数据库的更改,从而导致冲突。