我有Task
个具有多个属性的对象。这些任务在多个进程之间反弹(使用Celery),我想更新数据库中的任务状态。
每次更新都应仅更新对象的非NULL属性。到目前为止,我有类似的东西:
def del_empty_attrs(task):
for name in (key for key, val in vars(task).iteritems() if val is None):
delattr(task, name)
def update_task(session, id, **kw):
task = session.query(Task).get(id)
if task is None:
task = Task(id=id)
for key, value in kw.iteritems():
if not hasattr(task, key):
raise AttributeError('Task does not have {} attribute'.format(key))
setattr(task, key, value)
del_empty_attrs(task) # Don't update empty fields
session.merge(task)
但是,请获取IntegrityError
或StaleDataError
。这样做的正确方法是什么?
我认为问题是每个进程都有自己的session
,但我不确定。
答案 0 :(得分:0)
需要更多详细信息才能确定,但此代码中存在竞争条件:
def update_task(session, id, **kw):
# 1.
task = session.query(Task).get(id)
if task is None:
# 2.
task = Task(id=id)
for key, value in kw.iteritems():
if not hasattr(task, key):
raise AttributeError('Task does not have {} attribute'.format(key))
setattr(task, key, value)
del_empty_attrs(task) # Don't update empty fields
# 3.
session.merge(task)
如果两个进程都遇到#1,并且找到给定id的对象为None
,则它们都会继续使用给定的主键创建一个新的Task()
对象(假设为{{1}这是主键属性)。然后两个进程都向下竞争id
,它将尝试为该行发出INSERT。一个进程获取INSERT,另一个获取IntegrityError,因为它没有在另一个进程之前插入行。
对于如何“修复”这个没有简单的答案,这取决于你想要做什么。一种方法可能是确保没有两个进程在同一主键标识符池上工作。另一种方法是确保所有不存在行的INSERT都由单个进程处理。
编辑:其他方法可能涉及采用“乐观”方法,其中SAVEPOINT(例如Session.begin_nested())用于拦截INSERT上的IntegrityError,然后在发生后继续。