会话基础

会话的作用是什么?

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

Session 在其最常见的用例模式中,最初处于无状态形式。一旦发出查询或与之持久化其他对象,它就会向与 Session 关联的 Engine 请求一个连接资源,然后在该连接上建立一个事务。该事务将一直生效,直到 Session 被指示提交或回滚该事务。当事务结束时,与 Engine 关联的连接资源将被 释放 到由引擎管理的连接池。然后,一个新的事务将使用新的连接签出开始。

Session 维护的 ORM 对象是 带仪表的,因此,每当在 Python 程序中修改属性或集合时,就会生成一个更改事件,该事件将被 Session 记录。每当将要查询数据库或即将提交事务时,Session 首先会将存储在内存中的所有待处理更改 **刷新** 到数据库。这被称为 工作单元 模式。

当使用 Session 时,将维护的对象视为数据库行的 **代理对象** 是很有帮助的,这些代理对象对于 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 关联的所有对象都将 过期,这意味着它们的内容将被清除,以便在下一个事务中重新加载。如果这些对象被 分离,它们将无法正常工作,直到重新关联到一个新的 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()

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

# 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() 用于将实例放置到会话中。对于 瞬态(即全新的)实例,这将导致在下次刷新时对这些实例执行 INSERT 操作。对于 持久(即由该会话加载)的实例,它们已经存在,不需要添加。 分离(即已从会话中删除)的实例可以使用此方法重新与会话关联

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 级联级联。有关更多详细信息,请参阅 级联 部分。

删除

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

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

# commit (or flush)
session.commit()

Session.delete() 将对象标记为已删除,这将导致对每个受影响的主键发出 DELETE 语句。在待处理的删除被刷新之前,由“删除”标记的对象存在于 Session.deleted 集合中。在 DELETE 之后,它们从 Session 中删除,在事务提交后,这将变得永久。

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

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

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

  • 通过 relationship.secondary 参数链接为“多对多”表的行在所有情况下都会被删除,而不管它们引用的对象是否被删除。

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

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

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

另请参阅

delete - 描述“删除级联”,当删除主对象时,它将标记相关对象以供删除。

delete-orphan - 描述“删除孤儿级联”,当相关对象与其主对象分离时,它将标记相关对象以供删除。

关于删除的注意事项 - 删除来自集合和标量关系的引用对象 - 关于 Session.delete() 的重要背景信息,因为这涉及到关系在内存中的刷新。

刷新

Session 使用默认配置时,刷新步骤几乎总是透明地完成。具体来说,刷新发生在任何单个 SQL 语句发出之前,这些语句是作为 Query2.0 风格 Session.execute() 调用结果,以及在 Session.commit() 调用之前,在事务提交之前。它也发生在使用 Session.begin_nested() 时发出 SAVEPOINT 之前。

可以通过调用 Session.flush() 方法随时强制执行 Session 刷新。

session.flush()

在某些方法范围内自动发生的刷新被称为 **自动刷新**。自动刷新定义为一种可配置的、自动的刷新调用,它发生在以下方法的开始:

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

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

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

  • 当对象 刷新 时。

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

还有一些点会发生 **无条件** 刷新;这些点位于关键的事务边界内,包括:

将 **自动刷新** 行为应用于前面的项目列表,可以通过构造 Sessionsessionmaker 并将 Session.autoflush 参数设置为 False 来禁用。

Session = sessionmaker(autoflush=False)

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

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

**重申:** 刷新过程 **始终发生** 在调用 Session.commit()Session.begin_nested() 等事务方法时,无论任何“自动刷新”设置,只要 Session 还有待处理的更改,都会发生。

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

当刷新中发生错误时,为了继续使用同一个 Session,在刷新失败后,需要显式调用 Session.rollback(),即使底层事务已经回滚(即使数据库驱动程序在技术上处于驱动程序级别自动提交模式)。这样做是为了始终如一地维护所谓的“子事务”的整体嵌套模式。FAQ 部分 “此 Session 的事务因先前在刷新期间发生的异常而回滚。”(或类似信息) 包含了对此行为的更详细说明。

另请参阅

“此 Session 的事务因先前在刷新期间发生的异常而回滚。”(或类似信息) - 关于为什么当刷新失败时必须调用 Session.rollback() 的进一步背景信息。

按主键获取

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

my_user = session.get(User, 5)

The Session.get() 还包括用于复合主键值的调用形式,这些值可以作为元组或字典传递,以及允许特定加载器和执行选项的附加参数。请参阅 Session.get() 以获取完整的参数列表。

另请参阅

Session.get()

过期/刷新

在使用 Session 时,经常需要考虑的一个重要问题是处理从数据库加载的对象的状态,即如何将它们与当前事务的状态同步。SQLAlchemy ORM 基于 标识映射 的概念,因此当从 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()

有关 refresh / expire 概念的进一步讨论,请参见 刷新/过期

使用任意 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.begin() 方法显式地开始 Session 的事务状态。当调用此方法时,Session 将无条件地置于“事务”状态。 Session.begin() 可用作上下文管理器,如 构建 begin / commit / rollback 块 中所述。

禁用 Autobegin 以防止隐式事务

可以使用将 Session.autobegin 参数设置为 False 来禁用“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 的角度来看,这意味着 connection.commit() DBAPI 方法将在每个 DBAPI 连接上调用。

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

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

在此之后,假设Session绑定到一个EngineSession.commit()将提交当前已存在的数据库事务(如果有)。提交之后,与该事务相关的Connection对象将被关闭,导致其底层DBAPI连接被释放回与Session绑定的Engine关联的连接池。

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

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

最后,Session中的所有对象都将过期,因为事务已关闭。这样,当实例下次被访问时,无论是通过属性访问还是通过它们存在于SELECT的结果中,它们都会收到最新的状态。此行为可以通过Session.expire_on_commit标志进行控制,该标志可以设置为False,当此行为不可取时。

另请参阅

自动开始

回滚

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

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

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

版本 1.4 中已更改: 现在Session对象具有延迟“开始”行为,如autobegin中所述。如果没有开始事务,Session.commit()Session.rollback()等方法将不起作用。这种行为在 1.4 之前不会出现,因为在非自动提交模式下,始终会隐式存在事务。

Session.flush() 失败时,通常是由于主键、外键或“非空”约束违规等原因,系统会自动发出回滚操作(目前在部分失败后无法继续进行刷新)。但是,Session 在此时会进入一种称为“非活动”的状态,调用应用程序必须始终显式调用 Session.rollback() 方法,以便 Session 可以返回到可用状态(也可以直接关闭并丢弃)。有关进一步讨论,请参见 “此 Session 的事务已因先前在刷新期间发生的异常而回滚。”(或类似内容) 中的常见问题解答条目。

另请参阅

自动开始

关闭

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

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

可以通过设置参数 Session.close_resets_onlyFalse 来更改 Session.close() 的默认行为,这表示 Session 在调用方法 Session.close() 后无法重用。在这种操作模式下,方法 Session.reset() 将允许对会话进行多次“重置”,当 Session.close_resets_only 设置为 True 时,其行为与 Session.close() 相同。

版本 2.0.22 中新增。

建议 Session 的范围在最后通过调用 Session.close() 来限制,特别是如果未使用 Session.commit()Session.rollback() 方法。可以使用 Session 作为上下文管理器,以确保调用 Session.close()

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

# closes session automatically

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

会话常见问题解答

到目前为止,许多用户已经对会话有了疑问。本节介绍使用 Session 时遇到的最基本问题的简要常见问题解答(请注意,我们还有一个 真正的常见问题解答)。

我何时创建 sessionmaker

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

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

在本节中的示例中,我们经常会显示在实际调用 Session 的行上方创建的 sessionmaker。但这只是为了示例的方便!实际上,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,在子进程处理“作业”的整个生命周期中使用该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)

Changed in version 1.4: 现在Session 可作为上下文管理器使用,无需使用外部辅助函数。

Session 是缓存吗?

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

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

Session 不是为了成为一个全局对象,每个人都将其作为对象的“注册表”进行查询。这更像是 **二级缓存** 的工作。 SQLAlchemy 提供了一个使用dogpile.cache 实现二级缓存的模式,通过Dogpile 缓存 示例。

如何获取特定对象的Session

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

session = Session.object_session(someobject)

还可以使用较新的运行时检查 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(请注意,这些对象都支持一次绑定到多个引擎,但是在这种情况下,在事务范围内每个引擎仍然只有一个连接)。

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

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

因此,SQLAlchemy的SessionAsyncSession的并发模型是**每个线程一个Session,每个任务一个AsyncSession**。使用多个线程或在asyncio中使用多个任务(例如使用asyncio.gather()等API)的应用程序应该确保每个线程都有自己的Session,每个asyncio任务都有自己的AsyncSession.

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

对于那些受益于“全局”Session(无法将Session对象传递给需要它的特定函数和方法)的应用程序,scoped_session方法可以提供一个“线程本地”Session对象;请参阅上下文/线程本地会话部分了解背景信息。在asyncio上下文中,async_scoped_session对象是scoped_session的asyncio模拟,但配置起来更具挑战性,因为它需要一个自定义的“上下文”函数。