Session 基础

Session 是做什么的?

在最一般的意义上,Session 建立与数据库的所有对话,并代表您在其生命周期内加载或与之关联的所有对象的“暂存区”。它提供了进行 SELECT 和其他查询的接口,这些查询将返回和修改 ORM 映射的对象。ORM 对象本身维护在 Session 内部,在一个名为 identity map (标识映射)的结构中 - 一种数据结构,用于维护每个对象的唯一副本,其中“唯一”表示“只有一个具有特定主键的对象”。

Session 在其最常用的模式中,以几乎无状态的形式开始。一旦发出查询或使用它持久化其他对象,它就会从与 Session 关联的 Engine 请求连接资源,然后在该连接上建立事务。此事务一直有效,直到指示 Session 提交或回滚事务。当事务结束时,与 Engine 关联的连接资源被 released (释放)到引擎管理的连接池。然后,使用新的连接检出开始新的事务。

Session 维护的 ORM 对象是 instrumented (检测)的,这样,每当在 Python 程序中修改属性或集合时,都会生成一个更改事件,该事件由 Session 记录。每当要查询数据库时,或者当事务即将提交时,Session 首先 flushes (刷新)内存中存储的所有挂起的更改到数据库。这被称为 unit of work (工作单元)模式。

当使用 Session 时,将它维护的 ORM 映射对象视为数据库行的 proxy objects (代理对象)是有用的,这些代理对象是 Session 所持有的事务本地的。为了保持对象上的状态与数据库中实际内容匹配,有各种事件会导致对象重新访问数据库以保持同步。可以将对象从 Session 中 “detach” (分离),并继续使用它们,尽管这种做法有其注意事项。通常,当您想再次使用分离的对象时,您会将它们与另一个 Session 重新关联,以便它们可以恢复其表示数据库状态的正常任务。

使用 Session 的基础知识

此处介绍最基本的 Session 使用模式。

打开和关闭 Session

Session 可以单独构造,也可以使用 sessionmaker 类构造。它通常预先传递一个 Engine 作为连接源。典型的用法可能如下所示

from sqlalchemy import create_engine
from sqlalchemy.orm import Session

# an Engine, which the Session will use for connection
# resources
engine = create_engine("postgresql+psycopg2://scott:tiger@localhost/")

# create session and add objects
with Session(engine) as session:
    session.add(some_object)
    session.add(some_other_object)
    session.commit()

在上面,Session 使用与特定数据库 URL 关联的 Engine 实例化。然后在 Python 上下文管理器(即 with: 语句)中使用它,以便在代码块结束时自动关闭;这等效于调用 Session.close() 方法。

调用 Session.commit() 是可选的,仅当我们在 Session 中完成的工作包括要持久化到数据库的新数据时才需要。如果我们仅发出 SELECT 调用,而不需要写入任何更改,则调用 Session.commit() 是不必要的。

注意

请注意,在显式调用或使用上下文管理器调用 Session.commit() 后,与 Session 关联的所有对象都将 expired (过期),这意味着它们的内容将被擦除,以便在下一个事务中重新加载。如果这些对象改为 detached (分离),则它们将无法使用,直到与新的 Session 重新关联,除非使用 Session.expire_on_commit 参数禁用此行为。有关更多详细信息,请参阅 提交 部分。

构建 begin / commit / rollback 代码块

对于我们将数据提交到数据库的情况,我们还可以将 Session.commit() 调用和事务的整体“构建”包含在上下文管理器中。“构建”的意思是,如果所有操作都成功,将调用 Session.commit() 方法,但如果引发任何异常,将调用 Session.rollback() 方法,以便立即回滚事务,然后再向外传播异常。在 Python 中,这最基本地使用 try: / except: / else: 代码块表示,例如

# verbose version of what a context manager will do
with Session(engine) as session:
    session.begin()
    try:
        session.add(some_object)
        session.add(some_other_object)
    except:
        session.rollback()
        raise
    else:
        session.commit()

通过使用 Session.begin() 方法返回的 SessionTransaction 对象,可以更简洁地实现上述长序列操作,该对象为相同的操作序列提供了上下文管理器接口

# create session and add objects
with Session(engine) as session:
    with session.begin():
        session.add(some_object)
        session.add(some_other_object)
    # inner context calls session.commit(), if there were no exceptions
# outer context calls session.close()

更简洁地说,可以将两个上下文组合在一起

# create session and add objects
with Session(engine) as session, session.begin():
    session.add(some_object)
    session.add(some_other_object)
# inner context calls session.commit(), if there were no exceptions
# outer context calls session.close()

使用 sessionmaker

sessionmaker 的目的是为具有固定配置的 Session 对象提供工厂。由于应用程序通常在模块范围内具有 Engine 对象,因此 sessionmaker 可以为针对此引擎构造的 Session 对象提供工厂

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

# an Engine, which the Session will use for connection
# resources, typically in module scope
engine = create_engine("postgresql+psycopg2://scott:tiger@localhost/")

# a sessionmaker(), also in the same scope as the engine
Session = sessionmaker(engine)

# we can now construct a Session() without needing to pass the
# engine each time
with Session() as session:
    session.add(some_object)
    session.add(some_other_object)
    session.commit()
# closes the session

sessionmaker 类似于 Engine,作为函数级会话/连接的模块级工厂。因此,它也有自己的 sessionmaker.begin() 方法,类似于 Engine.begin(),它返回 Session 对象,并维护 begin/commit/rollback 代码块

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

# an Engine, which the Session will use for connection
# resources
engine = create_engine("postgresql+psycopg2://scott:tiger@localhost/")

# a sessionmaker(), also in the same scope as the engine
Session = sessionmaker(engine)

# we can now construct a Session() and include begin()/commit()/rollback()
# at once
with Session.begin() as session:
    session.add(some_object)
    session.add(some_other_object)
# commits the transaction, closes the session

在上面,当上面的 with: 代码块结束时,Session 将提交其事务,并且 Session 将被关闭。

当您编写应用程序时,sessionmaker 工厂的作用域应与 Engine 对象的作用域相同,该对象由 create_engine() 创建,通常在模块级或全局范围内。由于这些对象都是工厂,因此可以同时被任意数量的函数和线程使用。

另请参阅

sessionmaker

Session

查询

查询的主要方法是使用 select() 构造来创建 Select 对象,然后执行该对象以使用诸如 Session.execute()Session.scalars() 等方法返回结果。然后以 Result 对象的形式返回结果,包括诸如 ScalarResult 等子变体。

有关 SQLAlchemy ORM 查询的完整指南,请参阅 ORM 查询指南。以下是一些简短的示例

from sqlalchemy import select
from sqlalchemy.orm import Session

with Session(engine) as session:
    # query for ``User`` objects
    statement = select(User).filter_by(name="ed")

    # list of ``User`` objects
    user_obj = session.scalars(statement).all()

    # query for individual columns
    statement = select(User.name, User.fullname)

    # list of Row objects
    rows = session.execute(statement).all()

在版本 2.0 中更改: “2.0” 样式的查询现在是标准。有关从 1.x 系列迁移的说明,请参阅 2.0 迁移 - ORM 用法

另请参阅

ORM 查询指南

添加新的或已有的项

Session.add() 用于将实例放入会话中。对于 transient (瞬态,即全新的)实例,这将导致在下次刷新时对这些实例执行 INSERT 操作。对于 persistent (持久化,即由此会话加载的)实例,它们已经存在,无需添加。 detached (分离,即已从会话中删除的)实例可以使用此方法重新与会话关联

user1 = User(name="user1")
user2 = User(name="user2")
session.add(user1)
session.add(user2)

session.commit()  # write changes to the database

要一次将会话添加到会话中的项列表,请使用 Session.add_all()

session.add_all([item1, item2, item3])

Session.add() 操作沿着 save-update 级联 cascades (级联)。有关更多详细信息,请参阅 级联 部分。

删除

Session.delete() 方法将实例放入 Session 的对象列表中,以标记为已删除

# mark two objects to be deleted
session.delete(obj1)
session.delete(obj2)

# commit (or flush)
session.commit()

Session.delete() 将对象标记为删除,这将导致为每个受影响的主键发出 DELETE 语句。在刷新挂起的删除之前,标有“delete”的对象会出现在 Session.deleted 集合中。在 DELETE 操作之后,它们将从 Session 中 expunged (清除),这在事务提交后变为永久性的。

Session.delete() 操作相关的各种重要行为,尤其是在如何处理与其他对象和集合的关系方面。有关其工作原理的更多信息,请参阅 级联 部分,但总的来说,规则是

  • 通过 relationship() 指令与已删除对象相关的映射对象对应的行 默认情况下不会被删除。如果这些对象具有回到正在删除的行的外键约束,则这些列将设置为 NULL。如果列不可为空,则这将导致约束冲突。

  • 要将 “SET NULL” 更改为删除相关对象行的 DELETE 操作,请在 relationship() 上使用 delete 级联。

  • 通过 relationship.secondary 参数链接为 “多对多” 表的表中的行,在引用的对象被删除时 在所有情况下都将被删除

  • 当相关对象包含回到正在删除的对象的外键约束,并且它们所属的相关集合当前未加载到内存中时,工作单元将发出 SELECT 以获取所有相关行,以便可以使用它们的主键值在这些相关行上发出 UPDATE 或 DELETE 语句。通过这种方式,即使在 Core ForeignKeyConstraint 对象上配置了 ON DELETE CASCADE,ORM 也将在没有进一步指令的情况下执行 ON DELETE CASCADE 的功能。

  • relationship.passive_deletes 参数可用于调整此行为,并更自然地依赖 “ON DELETE CASCADE”;当设置为 True 时,将不再执行此 SELECT 操作,但是本地存在的行仍将受到显式的 SET NULL 或 DELETE 的影响。将 relationship.passive_deletes 设置为字符串 "all" 将禁用 所有 相关对象更新/删除。

  • 当对标记为删除的对象执行 DELETE 操作时,该对象不会自动从引用它的集合或对象引用中删除。当 Session 过期时,可以再次加载这些集合,以便不再存在该对象。但是,最好不要对这些对象使用 Session.delete(),而应从其集合中删除该对象,然后使用 delete-orphan,以便将其作为该集合删除的次要效果删除。有关此示例,请参阅 删除说明 - 从集合和标量关系引用的对象删除 部分。

另请参阅

delete - 描述 “delete cascade”(删除级联),它在删除主导对象时将相关对象标记为删除。

delete-orphan - 描述 “delete orphan cascade”(删除孤立项级联),它在相关对象与其主导对象解除关联时将它们标记为删除。

删除说明 - 从集合和标量关系引用的对象删除 - 关于 Session.delete() 的重要背景信息,因为它涉及内存中刷新的关系。

刷新

Session 与其默认配置一起使用时,刷新步骤几乎总是透明完成的。具体来说,刷新发生在由于 Query2.0-style Session.execute() 调用而发出的任何单个 SQL 语句之前,以及在提交事务之前的 Session.commit() 调用中。当使用 Session.begin_nested() 时,它也会在发出 SAVEPOINT 之前发生。

可以通过调用 Session.flush() 方法在任何时候强制执行 Session 刷新

session.flush()

在某些方法范围内自动发生的刷新称为 autoflush (自动刷新)。自动刷新定义为可配置的自动刷新调用,它发生在包括以下方法开始时:

  • Session.execute() 和其他 SQL 执行方法,当针对启用 ORM 的 SQL 构造(例如引用 ORM 实体和/或 ORM 映射属性的 select() 对象)使用时

  • 当调用 Query 以将 SQL 发送到数据库时

  • 在查询数据库之前的 Session.merge() 方法中

  • 当对象 refreshed (刷新)时

  • 当针对未加载的对象属性发生 ORM lazy load (延迟加载)操作时。

还有一些点会 unconditionally (无条件地)发生刷新;这些点在关键事务边界内,包括

可以通过构造 Sessionsessionmaker 并将 Session.autoflush 参数作为 False 传递来禁用应用于先前项目列表的 autoflush (自动刷新)行为

Session = sessionmaker(autoflush=False)

此外,可以使用 Session.no_autoflush 上下文管理器在使用 Session 的流程中临时禁用自动刷新

with mysession.no_autoflush:
    mysession.add(some_object)
    mysession.flush()

重申: 当调用诸如 Session.commit()Session.begin_nested() 等事务方法时,始终会发生 刷新过程,无论任何 “autoflush”(自动刷新)设置如何,只要 Session 还有剩余的挂起更改需要处理。

由于 Session 仅在 DBAPI 事务的上下文中调用数据库 SQL,因此所有 “flush” 操作本身仅发生在数据库事务中(受数据库事务的 隔离级别 的约束),前提是 DBAPI 未处于 驱动程序级别自动提交 模式。 这意味着,假设数据库连接在其事务设置中提供 原子性,如果在 flush 内部的任何单个 DML 语句失败,则整个操作将被回滚。

当 flush 内部发生故障时,为了继续使用同一个 Session,即使底层事务已经回滚(即使数据库驱动程序在技术上处于驱动程序级别的自动提交模式),在 flush 失败后也需要显式调用 Session.rollback()。 这是为了保持所谓的 “子事务” 的总体嵌套模式的一致性。 FAQ 部分 “由于之前在 flush 期间发生异常,此 Session 的事务已回滚。”(或类似) 包含对此行为的更详细描述。

另请参阅

“由于之前在 flush 期间发生异常,此 Session 的事务已回滚。”(或类似) - 关于为什么在 flush 失败时必须调用 Session.rollback() 的更多背景信息。

通过主键获取

由于 Session 使用 identity map,它通过主键引用当前的内存对象,因此提供了 Session.get() 方法,作为通过主键查找对象的一种手段,首先在当前的 identity map 中查找,然后查询数据库以获取不存在的值。 例如,要查找主键标识为 (5, )User 实体

my_user = session.get(User, 5)

Session.get() 也包括复合主键值的调用形式,可以作为元组或字典传递,以及允许特定加载器和执行选项的其他参数。 有关完整的参数列表,请参阅 Session.get()

另请参阅

Session.get()

过期 / 刷新

使用 Session 时经常出现的一个重要考虑因素是处理从数据库加载的对象上存在的状态,以便使其与事务的当前状态同步。 SQLAlchemy ORM 基于 identity map 的概念,这样,当从 SQL 查询中 “加载” 对象时,将维护与特定数据库标识对应的唯一 Python 对象实例。 这意味着,如果我们发出两个单独的查询,每个查询都针对同一行,并获取一个映射对象,则这两个查询将返回相同的 Python 对象

>>> u1 = session.scalars(select(User).where(User.id == 5)).one()
>>> u2 = session.scalars(select(User).where(User.id == 5)).one()
>>> u1 is u2
True

由此可见,当 ORM 从查询中获取行时,它将跳过已加载对象的属性填充。 这里的设计假设是假设事务是完全隔离的,然后在事务不是隔离的程度上,应用程序可以根据需要采取步骤,从数据库事务中刷新对象。 FAQ 条目 “我正在使用 Session 重新加载数据,但它没有看到我在其他地方提交的更改” 更详细地讨论了这个概念。

当 ORM 映射对象加载到内存中时,有三种通用方法可以使用当前事务中的新数据刷新其内容

  • expire() 方法 - Session.expire() 方法将擦除对象的选定或所有属性的内容,以便在下次访问它们时从数据库加载它们,例如使用 延迟加载 模式

    session.expire(u1)
    u1.some_attribute  # <-- lazy loads from the transaction
  • refresh() 方法 - 与之密切相关的是 Session.refresh() 方法,它执行 Session.expire() 方法所做的所有操作,但也会立即发出一个或多个 SQL 查询以实际刷新对象的内容

    session.refresh(u1)  # <-- emits a SQL query
    u1.some_attribute  # <-- is refreshed from the transaction
  • populate_existing() 方法或执行选项 - 这现在是一个执行选项,在 “填充现有项” 中进行了文档记录;在旧版本中,它在 Query 对象上作为 Query.populate_existing() 方法找到。 无论哪种形式,此操作都表明应从数据库中的内容无条件地重新填充从查询返回的对象

    u2 = session.scalars(
        select(User).where(User.id == 5).execution_options(populate_existing=True)
    ).one()

关于刷新/过期概念的进一步讨论,请参见 “刷新/过期”

使用任意 WHERE 子句的 UPDATE 和 DELETE

SQLAlchemy 2.0 包括增强的功能,用于发出多种 ORM 启用的 INSERT、UPDATE 和 DELETE 语句。 有关文档,请参阅 “ORM 启用的 INSERT、UPDATE 和 DELETE 语句” 文档。

自动开始

Session 对象具有一种称为 autobegin 的行为。 这表示只要使用 Session 执行任何工作,Session 就会在内部认为自身处于 “事务” 状态,这些工作包括修改 Session 的内部状态(关于对象状态更改),或者需要数据库连接的操作。

当首次构造 Session 时,不存在事务状态。 当调用诸如 Session.add()Session.execute() 之类的方法时,或者类似地,如果执行 Query 以返回结果(最终使用 Session.execute()),或者如果修改了 持久 对象的属性,则会自动开始事务状态。

可以通过访问 Session.in_transaction() 方法来检查事务状态,该方法返回 TrueFalse,指示 “autobegin” 步骤是否已进行。 虽然通常不需要,但 Session.get_transaction() 方法将返回表示此事务状态的实际 SessionTransaction 对象。

Session 的事务状态也可以通过显式调用 Session.begin() 方法来启动。 当调用此方法时,Session 将无条件地置于 “事务” 状态。 Session.begin() 可以用作上下文管理器,如 “构建 begin / commit / rollback 代码块” 中所述。

禁用自动开始以防止隐式事务

可以使用设置为 FalseSession.autobegin 参数来禁用 “autobegin” 行为。 通过使用此参数,Session 将要求显式调用 Session.begin() 方法。 在构造时,以及在调用 Session.rollback()Session.commit()Session.close() 方法中的任何一个之后,Session 都不会隐式地开始任何新事务,并且如果尝试在未首先调用 Session.begin() 的情况下使用 Session,则会引发错误

with Session(engine, autobegin=False) as session:
    session.begin()  # <-- required, else InvalidRequestError raised on next call

    session.add(User(name="u1"))
    session.commit()

    session.begin()  # <-- required, else InvalidRequestError raised on next call

    u1 = session.scalar(select(User).filter_by(name="u1"))

2.0 版本新增: 添加了 Session.autobegin,允许禁用 “autobegin” 行为

提交

Session.commit() 用于提交当前事务。 从本质上讲,这表示它在所有当前数据库连接上发出 COMMIT,这些连接都有正在进行的事务; 从 DBAPI 的角度来看,这意味着在每个 DBAPI 连接上调用 connection.commit() DBAPI 方法。

Session 没有事务时,表示自上次调用 Session.commit() 以来,此 Session 上未调用任何操作,该方法将开始并提交仅内部的 “逻辑” 事务,除非检测到挂起的 flush 更改,否则通常不会影响数据库,但仍会调用事件处理程序和对象过期规则。

Session.commit() 操作在相关数据库连接上发出 COMMIT 之前,无条件地发出 Session.flush()。 如果未检测到挂起的更改,则不会向数据库发出 SQL。 此行为不可配置,并且不受 Session.autoflush 参数的影响。

此后,假设 Session 绑定到 Engine,则 Session.commit() 将提交实际的数据库事务(如果已启动)。 提交后,与该事务关联的 Connection 对象将关闭,从而使其底层的 DBAPI 连接 释放 回与 Session 绑定的 Engine 关联的连接池。

对于绑定到多个引擎的 Session(例如,如 “分区策略” 中所述),对于在正在提交的 “逻辑” 事务中起作用的每个 Engine / Connection,将执行相同的 COMMIT 步骤。 除非启用 两阶段功能,否则这些数据库事务彼此不协调。

通过将 Session 直接绑定到 Connection,也可以使用其他连接交互模式; 在这种情况下,假定存在外部管理的事务,并且在这种情况下不会自动发出真正的 COMMIT; 有关此模式的背景信息,请参阅 “将 Session 加入到外部事务(例如用于测试套件)” 部分。

最后,当事务关闭时,Session 中的所有对象都将 过期。 这样做是为了在下次访问实例时,无论是通过属性访问还是通过它们出现在 SELECT 的结果中,它们都能接收到最新状态。 此行为可以通过 Session.expire_on_commit 标志来控制,当不需要此行为时,可以将其设置为 False

另请参阅

自动开始

回滚

Session.rollback() 回滚当前事务(如果有)。 当没有事务时,该方法静默地通过。

对于默认配置的会话,在通过 autobegin 或显式调用 Session.begin() 方法开始事务之后,会话的回滚后状态如下

在理解了该状态之后,Session 可以在回滚发生后安全地继续使用。

1.4 版本更改: Session 对象现在具有延迟的 “begin” 行为,如 autobegin 中所述。 如果未开始事务,则诸如 Session.commit()Session.rollback() 之类的方法无效。 在 1.4 之前,不会观察到此行为,因为在非自动提交模式下,事务将始终隐式存在。

Session.flush() 失败时,通常是由于主键、外键或 “不可为空” 约束冲突等原因,会自动发出 ROLLBACK(目前 flush 在部分失败后无法继续)。 但是,此时 Session 进入称为 “非活动” 的状态,调用应用程序必须始终显式调用 Session.rollback() 方法,以便 Session 可以返回到可用状态(也可以简单地关闭并丢弃)。 有关更多讨论,请参阅 FAQ 条目 “由于之前在 flush 期间发生异常,此 Session 的事务已回滚。”(或类似)

另请参阅

自动开始

关闭

Session.close() 方法发出 Session.expunge_all(),它从会话中删除所有 ORM 映射的对象,并从与其绑定的 Engine 对象中 释放 任何事务/连接资源。 当连接返回到连接池时,事务状态也会被回滚。

默认情况下,当 Session 关闭时,它基本上处于首次构造时的原始状态,并且可以再次使用。 从这个意义上讲,Session.close() 方法更像是 “重置” 回到干净状态,而不是很像 “数据库关闭” 方法。 在这种操作模式下,Session.reset() 方法是 Session.close() 的别名,并且行为方式相同。

可以通过将参数 Session.close_resets_only 设置为 False 来更改 Session.close() 的默认行为,这表示在调用 Session.close() 方法后,Session 不能被重用。在这种操作模式下,Session.reset() 方法将允许多次“重置”会话,其行为类似于当 Session.close_resets_only 设置为 True 时的 Session.close()

2.0.22 版本新增。

建议通过在最后调用 Session.close() 来限制 Session 的作用域,尤其是在不使用 Session.commit()Session.rollback() 方法的情况下。Session 可以用作上下文管理器,以确保调用 Session.close()

with Session(engine) as session:
    result = session.execute(select(User))

# closes session automatically

1.4 版本更改: Session 对象具有延迟 “begin” 行为,如 autobegin 中所述。在调用 Session.close() 方法后,不再立即开始新的事务。

Session 常见问题解答

到目前为止,许多用户已经对会话 (sessions) 产生了疑问。本节提供了一个迷你 FAQ(请注意,我们还有一个 real FAQ),介绍使用 Session 时遇到的一些最基本的问题。

我应该在什么时候创建 sessionmaker

只需一次,在应用程序的全局作用域中的某个位置。它应该被视为应用程序配置的一部分。如果您的应用程序在一个包中有三个 .py 文件,您可以例如将 sessionmaker 行放在您的 __init__.py 文件中;从那时起,您的其他模块就可以说 “from mypackage import Session”。这样,其他人只需使用 Session(),并且该会话的配置由该中心点控制。

如果您的应用程序启动、执行导入,但不知道它将连接到哪个数据库,您可以稍后在 “类” 级别将 Session 绑定到引擎,使用 sessionmaker.configure()

在本节的示例中,我们将经常展示 sessionmaker 在我们实际调用 Session 的行正上方创建。但这只是为了举例说明!实际上,sessionmaker 将位于模块级别上的某个位置。然后,对 Session 进行实例化的调用将放置在应用程序中数据库对话开始的点。

我应该在什么时候构造 Session,我应该在什么时候提交它,以及我应该在什么时候关闭它?

Session 通常在逻辑操作开始时构造,在该逻辑操作中可能会预期数据库访问。

每当 Session 用于与数据库通信时,它会在开始通信时立即开始数据库事务。此事务将保持进行中,直到 Session 被回滚、提交或关闭。如果在先前的事务结束后再次使用 Session,它将开始新的事务;由此可见,Session 能够跨越多个事务,尽管一次只能处理一个事务。我们将这两个概念称为 **事务作用域** 和 **会话作用域**。

通常不难确定开始和结束 Session 作用域的最佳时机,尽管各种可能的应用程序架构可能会带来具有挑战性的情况。

一些示例场景包括

  • Web 应用程序。在这种情况下,最好利用正在使用的 Web 框架提供的 SQLAlchemy 集成。或者,基本模式是在 Web 请求开始时创建一个 Session,在执行 POST、PUT 或 DELETE 的 Web 请求结束时调用 Session.commit() 方法,然后在 Web 请求结束时关闭会话。通常,将 Session.expire_on_commit 设置为 False 也是一个好主意,这样,如果事务已提交,则后续访问来自视图层中 Session 的对象时,无需发出新的 SQL 查询来刷新对象。

  • 一个派生子进程的后台守护程序将希望为每个子进程创建一个本地 Session,在该 fork 处理的 “作业” 的生命周期内使用该 Session,然后在作业完成时将其销毁。

  • 对于命令行脚本,应用程序将创建一个单一的全局 Session,该 Session 在程序开始工作时建立,并在程序完成任务时立即提交。

  • 对于 GUI 界面驱动的应用程序,Session 的作用域最好在用户生成的事件的作用域内,例如按钮按下。或者,作用域可能对应于显式的用户交互,例如用户 “打开” 一系列记录,然后 “保存” 它们。

作为一般规则,应用程序应在处理特定数据的功能外部管理会话的生命周期。这是一种基本的关注点分离,它使特定于数据的操作与其访问和操作该数据的上下文无关。

例如,不要这样做

### this is the **wrong way to do it** ###


class ThingOne:
    def go(self):
        session = Session()
        try:
            session.execute(update(FooBar).values(x=5))
            session.commit()
        except:
            session.rollback()
            raise


class ThingTwo:
    def go(self):
        session = Session()
        try:
            session.execute(update(Widget).values(q=18))
            session.commit()
        except:
            session.rollback()
            raise


def run_my_program():
    ThingOne().go()
    ThingTwo().go()

将会话(通常是事务)的生命周期保持分离和外部。下面的示例说明了这可能是什么样子,并且还使用了 Python 上下文管理器(即 with: 关键字)来自动管理 Session 及其事务的作用域。

### this is a **better** (but not the only) way to do it ###


class ThingOne:
    def go(self, session):
        session.execute(update(FooBar).values(x=5))


class ThingTwo:
    def go(self, session):
        session.execute(update(Widget).values(q=18))


def run_my_program():
    with Session() as session:
        with session.begin():
            ThingOne().go(session)
            ThingTwo().go(session)

1.4 版本更改: Session 可以用作上下文管理器,而无需使用外部辅助函数。

Session 是缓存吗?

嗯…不是。它在某种程度上用作缓存,因为它实现了 identity map 模式,并将对象存储为以其主键为键。但是,它不进行任何类型的查询缓存。这意味着,如果您说 session.scalars(select(Foo).filter_by(name='bar')),即使 Foo(name='bar') 就在那里,在 identity map 中,会话也不知道这一点。它必须向数据库发出 SQL,取回行,然后在看到行中的主键时,才能在本地 identity map 中查找并查看该对象是否已存在。只有当您说 query.get({some primary key}) 时,Session 才不必发出查询。

此外,Session 默认使用弱引用存储对象实例。这也破坏了将 Session 用作缓存的目的。

Session 并非设计为全局对象,每个人都从中查询作为对象的 “注册表”。这更像是 **二级缓存** 的工作。SQLAlchemy 提供了一种使用 dogpile.cache 实现二级缓存的模式,通过 Dogpile Caching 示例。

如何获取特定对象的 Session

使用 Session.object_session() 类方法,该方法在 Session 上可用。

session = Session.object_session(someobject)

较新的 Runtime Inspection API 系统也可以使用。

from sqlalchemy import inspect

session = inspect(someobject).session

Session 是线程安全的吗?AsyncSession 可以安全地在并发任务中共享吗?

Session 是一个 **可变的、有状态的** 对象,它表示 **单个数据库事务**。因此,在没有仔细同步的情况下,Session 的实例 不能在并发线程或 asyncio 任务之间共享Session 旨在以 **非并发** 方式使用,也就是说,Session 的特定实例一次只能在一个线程或任务中使用。

当使用 SQLAlchemy 的 asyncio 扩展中的 AsyncSession 对象时,此对象只是 Session 之上的一个轻量级代理,并且相同的规则适用;它是一个 **不同步的、可变的、有状态的** 对象,因此 **不** 安全在多个 asyncio 任务中同时使用 AsyncSession 的单个实例。

SessionAsyncSession 的实例表示单个逻辑数据库事务,一次仅引用特定 EngineAsyncEngine 的单个 Connection,该对象绑定到这些 EngineAsyncEngine(请注意,这些对象都支持一次绑定到多个引擎,但是在这种情况下,在事务范围内,每个引擎仍然只有一个连接在起作用)。

事务中的数据库连接也是一个有状态的对象,旨在以非并发、顺序的方式进行操作。命令按顺序在连接上发出,数据库服务器按照它们发出的确切顺序处理这些命令。当 Session 在此连接上发出命令并接收结果时,Session 本身正在经历内部状态更改,这些更改与此连接上存在的命令和数据的状态对齐;状态包括事务是否已开始、提交或回滚,是否有 SAVEPOINT 在起作用,以及单个数据库行状态与本地 ORM 映射对象的细粒度同步。

在为并发设计数据库应用程序时,合适的模型是每个并发任务/线程都使用自己的数据库事务。这就是为什么在讨论数据库并发问题时,使用的标准术语是 **多个并发事务**。在传统的 RDMS 中,对于接收和并发处理多个命令的单个数据库事务,没有类似的对应物。

因此,SQLAlchemy 的 SessionAsyncSession 的并发模型是 **每个线程一个 Session,每个任务一个 AsyncSession**。使用多个线程或 asyncio 中多个任务(例如,当使用像 asyncio.gather() 这样的 API 时)的应用程序将需要确保每个线程都有自己的 Session,每个 asyncio 任务都有自己的 AsyncSession

确保这种用法的最佳方法是在线程或任务内部的顶层 Python 函数中本地使用 标准上下文管理器模式,这将确保 SessionAsyncSession 的生命周期在本地作用域内得到维护。

对于那些受益于拥有 “全局” Session 的应用程序,在这些应用程序中,将 Session 对象传递给需要它的特定函数和方法不是一种选择,scoped_session 方法可以为 “线程本地” Session 对象提供支持;有关背景信息,请参阅 Contextual/Thread-local Sessions 部分。在 asyncio 上下文中,async_scoped_session 对象是 scoped_session 的 asyncio 类似物,但配置起来更具挑战性,因为它需要自定义 “context” 函数。