SQLAlchemy - 多线程持久对象创建,如何合并回单个会话以避免状态冲突?

时间:2013-06-26 02:38:58

标签: python sqlalchemy flask-sqlalchemy

由于需要处理,我想要以多线程方式生成数十个(可能数百个)数千个持久对象。

虽然对象的创建发生在不同的线程中(使用带有作用域会话的Flask-SQLAlchemy扩展btw),但生成完成后,将生成的对象写入DB的调用发生在1个位置。

我认为问题在于,正在创建的对象是几个现有关系的一部分 - 从而触发自动添加到身份映射,尽管它是在单独的并发线程中创建的,在任何线程中都没有显式会话

我希望在单个列表中包含生成的对象,然后将整个列表(使用单个会话对象)写入数据库。这会导致如下错误:

AssertionError: A conflicting state is already present in the identity map for key (<class 'app.ModelObject'>, (1L,))

因此,为什么我认为身份映射已经填充,因为当我尝试使用并发代码的全局会话外部添加和提交时,会触发断言错误。

最后的细节是无论会话对象,(作用域或其他方面,因为我不完全理解在多线程的情况下如何自动添加到身份地图)我找不到方法/不知道如何获得对它们的引用,以便即使我想处理每个进程的单独会话,我也可以。

非常感谢任何建议。我之前没有发布代码的唯一原因是因为很难从我的应用程序中立即抽象出一个工作示例。如果有人真的需要看,我会发帖。

2 个答案:

答案 0 :(得分:4)

每个会话都是线程本地的;换句话说,每个线程都有一个单独的会话。如果您决定将某些实例传递给另一个线程,它们将与会话“分离”。在接收线程中使用db.session.add_all(objects)将它们全部放回去。

出于某种原因,您似乎在不同的线程中创建具有相同标识(主键列)的对象,然后尝试将它们都发送到数据库。一种选择是修复发生这种情况的原因,以便保证身份的唯一性。您也可以尝试merging; merged_object = db.session.merge(other_object, load=False)

编辑:zzzeek的评论让我了解了可能发生的其他事情:

使用Flask-SQLAlchemy,会话与应用程序上下文相关联。由于这是线程本地的,因此生成新线程将使上下文无效;线程中没有数据库会话。所有实例都在那里分离,无法正确跟踪关系。一种解决方案是将app传递给每个线程并执行with app.app_context():块内的所有内容。在块内,首先使用db.session.add使用传递的实例填充本地会话。您之后仍应合并主任务以确保一致性。

答案 1 :(得分:3)

我只是想澄清一下这个问题以及一些伪代码的解决方案,以防有人遇到这个问题/希望将来这样做。

class ObjA(object):
    obj_c = relationship('ObjC', backref='obj_c')

class ObjB(object):
    obj_c = relationship('ObjC', backref='obj_c')

class ObjC(object):
    obj_a_id = Column(Integer, ForeignKey('obj_a.id'))
    obj_b_id = Column(Integer, ForeignKey('obj_b.id'))

    def __init__(self, obj_a, obj_b):
        self.obj_a = obj_a
        self.obj_b = obj_b


def make_a_bunch_of_c(obj_a, list_of_b=None):
    return [ObjC(obj_a, obj_b) for obj_b in list_of_b]

def parallel_generate():
   list_of_a = session.query(ObjA).all() # assume there are 1000 of these
   list_of_b = session.query(ObjB).all() # and 30 of these

   fxn = functools.partial(make_a_bunch_of_c, list_of_b=list_of_b)
   pool = multiprocessing.Pool(10)
   all_the_things = pool.map(fxn, list_of_a)
   return all_the_things

现在让我们停在这里一秒钟。最初的问题是尝试添加ObjC列表导致原始问题中的错误消息:

session.add_all(all_the_things)

AssertionError: A conflicting state is already present in the identity map for key [...]

注意:在添加阶段发生错误,提交尝试从未发生过,因为断言发生在提交前。据我所知。

解决方案:

all_the_things = parallel_generate()
for thing in all_the_things:
    session.merge(thing)
session.commit()

处理自动添加的对象(通过关系级联)时会话利用的细节仍然超出我的范围,我无法解释为什么最初发生冲突。我所知道的是,使用merge函数将导致SQLAlchemy将在10个不同进程中创建的所有子对象排序到主进程中的单个会话中。

如果有人发生这种情况,我会很好奇。