python-3.x sqlalchemy对象何时变得“非持久”

8dtrkrch  于 2023-03-04  发布在  Python
关注(0)|答案(3)|浏览(212)

我有一个函数,它有一个半长时间运行的会话,用于处理一堆数据库行...在某个时刻,我希望重新加载或“刷新”其中一行,以确保状态没有发生任何变化。大多数时候,此代码工作正常,但我不时会遇到此错误

sqlalchemy.exc.InvalidRequestError: Instance '<Event at 0x58cb790>' is not persistent within this Session

我一直在阅读状态,但不明白为什么一个对象会停止持久化?我仍然在会话中,所以我不知道为什么我会停止持久化。
有人能解释一下是什么导致我的对象在会话中“非持久化”吗?在此之前我没有对该对象进行任何写入。
下面的 db_event 是将变为“非持久性”的对象

async def event_white_check_mark_handler(
    self: Events, ctx, channel: TextChannel, member: discord.Member, message: Message
):
    """
    This reaction is for completing an event
    """
    session = database_objects.SESSION()
    try:
        message_id = message.id
        db_event = self.get_event(session, message_id)
        if not db_event:
            return
        logger.debug(f"{member.display_name} wants to complete an event {db_event.id}")
        db_guild = await db.get_or_create(
            session, db.Guild, name=channel.guild.name, discord_id=channel.guild.id
        )
        db_member = await db.get_or_create(
            session,
            db.Member,
            name=member.name,
            discord_id=member.id,
            nick=member.display_name,
            guild_id=db_guild.discord_id,
        )
        db_scheduler_config: db.SchedulerConfig = (
            session.query(db.SchedulerConfig)
            .filter(db.SchedulerConfig.guild_id == channel.guild.id)
            .one()
        )
        # reasons to not complete the event
        if len(db_event) == 0:
            await channel.send(
                f"{member.display_name} you cannot complete an event with no one on it!"
            )
        elif (
            db_member.discord_id == db_event.creator_id
            or await db_scheduler_config.check_permission(
                ctx, db_event.event_name, member, db_scheduler_config.MODIFY
            )
        ):
            async with self.EVENT_LOCKS[db_event.id]:
                session.refresh(db_event)                ###########  <---- right here is when I get the error thrown
                db_event.status = const.COMPLETED
                session.commit()
                self.DIRTY_EVENTS.add(db_event.id)

            member_list = ",".join(
                filter(
                    lambda x: x not in const.MEMBER_FIELD_DEFAULT,
                    [str(x.mention) for x in db_event.members],
                )
            )
            await channel.send(f"Congrats on completing a event {member_list}!")
            logger.info(f"Congrats on completing a event {member_list}!")
            # await self.stop_tracking_event(db_event)
            del self.REMINDERS_BY_EVENT_ID[db_event.id]

        else:
            await channel.send(
                f"{member.display_name} you did not create this event and do not have permission to delete the event!"
            )
            logger.warning(f"{member.display_name} you did not create this event!")
    except Exception as _e:
        logger.error(format_exc())
        session.rollback()
    finally:
        database_objects.SESSION.remove()
lzfw57am

lzfw57am1#

我相当肯定这种情况的根本原因是争用条件。在默认配置中使用scoped session仅基于线程来管理作用域。在顶部使用协程可能意味着2个或更多个最终共享同一个会话,并且在event_white_check_mark_handler的情况下,它们会争用提交/回滚并从作用域会话注册表中删除会话。有效地关闭它,并从现在失效的会话中删除所有剩余的示例,使其他协程不高兴。
一个解决方案是在event_white_check_mark_handler中根本不使用作用域会话,因为它完全管理会话的生存期,并且似乎将会话作为参数向前传递。另一方面,如果有一些路径使用作用域会话database_objects.SESSION而不是将会话作为参数接收,请在创建注册表时定义一个合适的scopefunc

pinkon5k

pinkon5k2#

我在从生成器检索会话并尝试从不同的生成会话再次运行完全相同的查询时遇到了这个问题:

SessionLocal = sessionmaker(bind=engine, class_=Session)
    
def get_session() -> Generator:
    with SessionLocal() as session:
        yield session

解决方案是直接使用session(在我的情况下),也许在您的情况下,我会在执行新查询之前提交session。

def get_data():
    with Session(engine) as session:
        statement = select(Company)
        results = session.exec(statement)
yqlxgs2m

yqlxgs2m3#

在我的例子中,我使用Tornado我放置了一个这样的函数-在BaseClass

def __del__(self):
  if self.session:
     self.session.close()

因为我不想在任何继承类中实现这个__del__函数
发生的问题是因为当一个类被销毁(__del__被触发并关闭了我的连接),然后再次被调用,但没有带来我的会话(在我的init类中,我创建了它)。所以当我试图重新使用它时,连接被关闭。
我的解决方案是-将这个通用的高级__del__移动到每个类中。

相关问题