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

Python 多线程持久对象创建,如何合并回单个会话以避免状态冲突?,python,sqlalchemy,flask-sqlalchemy,Python,Sqlalchemy,Flask Sqlalchemy,由于需要进行处理,我希望以多线程方式生成数十(可能数百)个持久对象 虽然对象的创建在单独的线程中进行(使用Flask SQLAlchemy extension btw和作用域会话),但将生成的对象写入DB的调用在生成完成后的1个位置进行 我认为,问题在于,所创建的对象是几个现有关系的一部分——因此触发了自动添加到标识映射的过程,尽管这些对象是在单独、并发的线程中创建的,并且在任何线程中都没有显式的会话 我希望在单个列表中包含生成的对象,然后将整个列表(使用单个会话对象)写入数据库。这会导致如下错

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

虽然对象的创建在单独的线程中进行(使用Flask SQLAlchemy extension btw和作用域会话),但将生成的对象写入DB的调用在生成完成后的1个位置进行

我认为,问题在于,所创建的对象是几个现有关系的一部分——因此触发了自动添加到标识映射的过程,尽管这些对象是在单独、并发的线程中创建的,并且在任何线程中都没有显式的会话

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

AssertionError: A conflicting state is already present in the identity map for key (<class 'app.ModelObject'>, (1L,))
AssertionError:密钥(,(1L,)的标识映射中已存在冲突状态
因此,我认为标识映射已经填充,因为当我尝试在并发代码的外部使用全局会话添加和提交时,会触发断言错误

最后一个细节是,无论会话对象是什么(范围或其他,因为我不完全了解在多线程的情况下如何自动添加到标识映射),我都找不到方法/不知道如何获取对它们的引用,因此即使我想处理每个进程的单独会话,我也可以


非常感谢您的建议。我现在还没有发布代码的唯一原因是,很难立即从我的应用程序中提取一个工作示例。如果有人真的需要查看,我会发布。

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

出于某种原因,看起来您正在不同的线程中创建具有相同标识(主键列)的对象,然后尝试将它们都发送到数据库。一种选择是修复发生这种情况的原因,从而确保身份是唯一的。你也可以试试
merged\u object=db.session.merge(其他\u object,load=False)

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


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

我只想用一些伪代码来澄清问题和解决方案,以防将来有人遇到此问题/希望这样做

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个不同进程创建的所有子对象排序到主进程中的单个会话中


如果有人遇到这种情况,我会好奇为什么。

我对flask sqlalchemy的理解是,它不是基于线程本地分配会话,而是基于每个请求分配会话。但是我真的不明白你是如何在web请求的上下文中运行多线程任务的?这完全取决于您的会话创建/使用模式,这里不清楚。让我澄清一下。此特定代码是在Flask的请求上下文之外从CLI运行的。我担心的是,模型类仍然使用Flask SQLAlchemy创建的声明性基,并且由于生成的对象是子对象,因此在创建它们并与它们的父对象(已经是持久的)关联时,它们会自动添加到标识映射中CLI脚本从未传递任何显式会话引用,因此我不确定如何处理正在发生的冲突。我只能假设每个线程都在动态地创建一个。我将在大约一个小时内提取并上传一些示例代码,感谢您对这个问题的关注。最终是您的文档的彻底性帮助我找到了一个解决方案:)分离状态假设实际上是我想在这里澄清的缺失细节。由于对象与已持久化(在活动会话中)的父对象相关联,子对象(在子进程中)是真正分离的还是由于关联而暂时/挂起的?