从单独的进程更新对象字段? (有点心)

时间:2013-12-20 01:14:16

标签: sqlalchemy

我有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)

但是,请获取IntegrityErrorStaleDataError。这样做的正确方法是什么?

认为问题是每个进程都有自己的session,但我不确定。

1 个答案:

答案 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,然后在发生后继续。