关于 python:SQLAlchemy 会话的上下文/范围是否需要非自动对象/属性过期?

Does Context/Scoping of a SQLAlchemy Session Require Non-Automatic Object/Attribute Expiration?

情况:具有基本属性的简单类

在我正在处理的应用程序中,特定类的实例在其生命周期结束时被持久化,虽然它们随后不会被修改,但可能需要读取它们的属性。例如,实例的 end_time 或其相对于同一类的其他实例的序数位置(初始化的第一个实例的值为 1,下一个实例的值为 2 等)。

1
2
3
4
5
6
7
8
9
10
class Foo(object):
    def __init__(self, position):
        self.start_time = time.time()
        self.end_time = None
        self.position = position
        # ...
    def finishFoo(self):
        self.end_time = time.time()
        self.duration = self.end_time - self.start_time
    # ...

目标:使用 SQLAlchemy 持久化实例

遵循我认为的最佳实践 - 使用范围 SQLAlchemy Session,如此处建议的那样,通过 contextlib.contextmanager - 我将实例保存在新创建的 Session 中,该实例会立即提交。下一行通过在日志记录中提及它来引用新的持久性实例,这会引发 DetachedInstanceError,因为当 Session 提交时,其引用的属性已过期。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Database(object):
    # ...
    def scopedSession(self):
        session = self.sessionmaker()
        try:
            yield session
            session.commit()
        except:
            session.rollback()
            logger.warn("blah blah blah...")
        finally:
            session.close()
    # ...
    def saveMyFoo(self, foo):
        with self.scopedSession() as sql_session:
            sql_session.add(foo)
        logger.info("Foo number {0} finished at {1} has been saved."
                   "".format(foo.position, foo.end_time))
        ## Here the DetachedInstanceError is raised

两种已知的可能解决方案:无到期或无范围

我知道我可以将 expire_on_commit 标志设置为 False 来规避这个问题,但我担心这是一个有问题的做法——自动过期的存在是有原因的,而且我不愿意随意将所有 ORM 混为一谈——在没有充分理由和理解的情况下将类绑定到非到期状态。或者,我可以忘记定义 Session 的范围,然后让事务挂起,直到我在(很久)以后明确提交。

所以我的问题归结为:

  • 在我描述的情况下,是否正确使用了作用域/上下文管理的 Session
  • 是否有替代方法来引用过期属性是更好/更首选的方法? (例如,使用属性来package捕获过期/分离异常的步骤或创建


    1.

    很可能,是的。就将数据正确保存到数据库而言,它的使用是正确的。但是,由于您的事务仅跨越更新,因此在更新同一行时可能会遇到竞争条件。根据应用程序,这可能没问题。

    2.

    不让属性过期是正确的做法。默认情况下到期的原因是因为它确保即使是幼稚的代码也能正常工作。如果你小心点,应该不会有问题。

    3.

    将事务的概念与会话的概念分开很重要。 contextmanager 做两件事:它维护会话以及事务。每个 ORM 实例的生命周期仅限于每个事务的跨度。这样您就可以假设对象的状态与数据库中相应行的状态相同。这就是框架在您提交时使属性过期的原因,因为它无法再保证事务提交后值的状态。因此,您只能在事务处于活动状态时访问实例的属性。

    提交后,您访问的任何后续属性都将导致启动新事务,以便 ORM 可以再次保证数据库中值的状态。

    但是为什么会出现错误?这是因为您的会话已消失,因此 ORM 无法启动事务。如果您在上下文管理器块的中间执行 session.commit() 操作,如果您访问其中一个属性,您会注意到一个新事务正在启动。

    好吧,如果我只想访问先前获取的值怎么办?然后,您可以要求框架不要使这些属性过期。