会话基础


会话有什么作用?


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


Session 最常见的使用模式以大部分无状态的形式开始。发出查询或保留其他对象后,它会从与 Session 关联的 Engine 请求连接资源,然后在该连接上建立事务。此交易在 Session 之前一直有效 被指示提交或回滚事务。 当事务 ends,则与引擎关联的连接资源 释放到引擎管理的连接池中。然后,新事务从新的 connection checkout 开始。


Session 维护的 ORM 对象被检测 这样,每当在 Python 中修改属性或集合时,都会显示 程序中,会生成一个 change 事件,该事件由 会话。每当要查询数据库或即将提交事务时,首先执行 Session 内存中存储的所有待处理更改刷新到数据库。这称为 Unit of work 模式。


当使用 Session 时,将它作为代理对象维护的 ORM 映射对象视为数据库行是有用的,这些对象是 Session 持有的事务的本地对象。 为了维护 state 匹配数据库中的实际内容,则有一个 各种事件,这些事件将导致对象重新访问数据库,以便 保持同步。 可以将对象从 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 使用 Engine 实例化 与特定数据库 URL 相关联。 然后在 Python 中使用它 上下文管理器(即 with: 语句),以便在块的末尾自动关闭;这相当于调用 Session.close() 方法。


Session.commit() 的调用是可选的,只有当我们对 Session 所做的工作包含要持久化到数据库的新数据时,才需要调用它。如果我们只发出 SELECT 调用并且不需要编写任何更改,那么对 Session.commit() 的调用就没有必要了。


注意


请注意,在调用 Session.commit() 后,显式或 使用上下文管理器时,与 会话已过期,这意味着它们的内容被擦除到 在下一个事务中重新加载。如果这些对象是 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


在上述情况下,Session 将同时提交其事务,并且 Session 将被关闭,此时上述 with: 块结束。


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


另请参阅


会话生成器


会期


查询


查询的主要方法是使用 select() construct 创建一个 Select 对象,然后使用 Session.execute()Session.scalars() 的 然后,结果将以 Result 对象,包括 ScalarResult 的 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” 样式的查询现在是标准的。 看 2.0 迁移 - 1.x 系列迁移说明的 ORM 用法。


另请参阅


ORM 查询指南


添加新项目或现有项目


Session.add() 用于将实例放置在 session 中。对于临时 (即全新的) 实例,这将产生在下一次 flush 时对这些实例执行 INSERT 的效果。对于持久性实例(即由此 session 加载),它们已经存在,不需要添加。已分离的实例 (即已从会话中删除)可能会重新与会话关联 使用此方法:

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() 方法将一个实例放入 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 中删除,这在事务提交后成为永久性的。


有各种与 Session.delete()作,尤其是在如何处理与其他对象和集合的关系方面。在 Cascades 部分有更多关于它是如何工作的信息,但一般来说,规则是:


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


  • 要将 “SET NULL” 更改为相关对象行的 DELETE,请使用 delete relationship() 上的 cascade。


  • 作为“多对多”表链接的表中的行,通过 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 Cascade”,它在删除潜在客户对象时将相关对象标记为要删除。


delete-orphan - 描述“删除孤立级联”,当相关对象与其引导对象取消关联时,它会将相关对象标记为要删除。


有关删除的说明 - 删除从集合和标量关系引用的对象 - 重要背景 Session.delete() 涉及在内存中刷新的关系。


刷新


Session 与其默认配置一起使用时,flush 步骤几乎总是透明地完成的。具体来说,刷新发生在由于 Query2.0 样式Session.execute() 调用而发出任何单个 SQL 语句之前,以及在 Session.commit() 调用。 承诺。在以下情况下,它也发生在发出 SAVEPOINT 之前 使用 Session.begin_nested()。


可以随时通过调用 Session.flush() 方法:

session.flush()


在某些方法范围内自动发生的 flush 称为 autoflush。Autoflush 被定义为可配置的自动 flush 调用,它发生在方法的开头,包括:


也有一些点会无条件地发生 flush;这些点位于关键的交易边界内,其中包括:


应用于前面的项目列表的 autoflush 行为可以通过构造 SessionsessionmakerSession.autoflush 参数作为 错误

Session = sessionmaker(autoflush=False)


此外,可以在使用 Session 的流程中使用 Session.no_autoflush 上下文管理器:

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


重申一下:当 Session 有剩余的待处理更改时,无论 Session.commit()Session.begin_nested() 等事务方法被调用时,无论任何 “autoflush” 设置如何,刷新进程总是发生的


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


当 flush 中发生故障时,为了继续使用同一个 Session,对 Session.rollback() 的显式调用是 required,即使底层事务将具有 已回滚(即使数据库驱动程序在技术上处于 驱动程序级自动提交模式)。 这样,整个 所谓的 “subtransactions” 被始终维护。FAQ 部分 “由于刷新期间的上一个异常,此 Session 的事务已回滚。”(或类似名称)包含此行为的更详细描述。


另请参阅


“由于刷新期间的上一个异常,此 Session 的事务已回滚。”(或类似名称)- 有关原因的更多背景 当 flush 失败时,必须调用 Session.rollback()


按主键获取


由于 Session 使用身份映射,该 Map 通过主键引用当前的内存中对象,因此 Session.get() method 作为通过主键定位对象的一种方式,首先 在当前身份映射中查找,然后查询数据库 对于非现值。 例如,要查找具有主键身份 (5, ) 的 User 实体:

my_user = session.get(User, 5)


Session.get() 还包括复合主键值的调用形式,这些值可以作为 Tuples 或 Dictionaries 传递,以及允许特定 loader 和执行选项的附加参数。有关完整的参数列表,请参见 Session.get()


另请参阅


会话.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 从查询中返回行时,它将 跳过已加载的对象的属性填充。这里的设计假设是假设一个事务是完全隔离的,然后根据该事务未隔离的程度,应用程序可以根据需要采取步骤来刷新数据库事务中的对象。我正在使用我的会话重新加载数据,但它没有看到我在其他地方提交的更改 更详细地讨论了这个概念。


当 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() 方法或执行选项 - 这是现在在 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-Enabled INSERT、UPDATE 和 DELETE 语句中的文档。


自动开始


Session 对象具有一种称为 autobegin 的行为。这表明 Session 将在内部考虑自身 在执行任何工作后立即处于 “transactional” 状态 Session,要么涉及对 Session 内部状态的修改,要么涉及需要数据库连接的作。


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


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


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


禁用 Autostart 以防止隐式事务


可以使用 Session.autobegin 参数设置为 False。通过使用此参数,Session 将要求 Session.begin() 方法。在构造时以及任何 Session.rollback() 之后, Session.commit()Session.close() 方法,Session 不会隐式地开始任何新的事务,如果尝试使用 Session 而没有先调用 Session.begin(),则会引发错误:

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 上调用任何作,该方法将开始并提交一个仅限内部的“逻辑”事务,除非检测到待处理的刷新更改,否则该事务通常不会影响数据库,但仍将调用事件处理程序和对象过期规则。


Session.commit()作无条件地发出 Session.flush() 在相关数据库上发出 COMMIT 之前 连接。如果未检测到待处理更改,则不会向 数据库。此行为不可配置,也不受 Session.autoflush 参数。


之后,假设 Session 绑定到 Engine 时,Session.commit() 将 COMMIT 实际的数据库事务(如果已启动)。提交后,与该事务关联的 Connection 对象将关闭,从而导致其底层 DBAPI 连接被释放回与引擎关联的连接池,该引擎的 会话已绑定。


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


其他连接交互模式也可用,只需将 Session 直接连接到 Connection;在本例中, 它假定存在外部管理的事务,并且一个真实的 在这种情况下,不会自动发出 COMMIT;请参阅该部分 将 Session 加入 External Transaction (例如测试套件) 以获取有关此模式的背景信息。


最后,当事务被关闭时,Session 中的所有对象都会过期。这样,当下次通过属性访问或通过它们出现在 SELECT 结果中访问实例时,它们会收到最新的状态。此行为可能由 Session.expire_on_commit 标志控制,当此行为不可取时,该标志可能设置为 False


另请参阅


自动开始


回滚


Session.rollback() 回滚当前事务(如果有)。当没有事务时,该方法以静默方式传递。


使用默认配置的会话,会话的回滚后状态,在事务通过 autobegin 开始之后 或通过调用 Session.begin() method 显式地表示,如下所示:


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


在 1.4 版本发生变更: Session 对象现在具有延迟的 “begin” 行为,如 autobegin 中所述。如果没有事务开始,则 Session.commit()Session.rollback() 无效。在 1.4 之前不会观察到此行为,因为在非 auto-commit 模式下,事务将始终隐式存在。


Session.flush() 失败时,通常是由于主键、外键或“不可为空”约束冲突等原因,会自动发出 ROLLBACK(目前在部分失败后无法继续刷新)。但是,Session 会进入一种称为 “inactive”,并且调用应用程序必须始终调用 Session.rollback() 方法,以便 Session 可以返回到可用状态(也可以简单地关闭并丢弃)。请参阅“此会话的事务已因刷新期间的先前异常而被回滚”中的常见问题解答条目。(或类似名称)以供进一步讨论。


另请参阅


自动开始


关闭


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


默认情况下,当 Session 关闭时,它基本上处于第一次构造时的原始状态,并且可以再次使用。从这个意义上说,Session.close() 方法更像是 “reset” 回到 clean 状态,而不是 “database close” 方法。在这种作模式下,方法 Session.reset()Session.close() 的 Session.close() 方法,并且其行为方式相同。


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


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 常见问题


此时,许多用户已经对会话有疑问。本节介绍了一个迷你 FAQ(请注意,我们还有一个真正的 FAQ),列出了使用 Session 时遇到的最基本问题。


我何时创建 sessionmaker


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


如果您的应用程序启动,则执行导入,但不知道导入内容 数据库,则可以将 稍后使用 sessionmaker.configure() 将 session 添加到引擎。


在本节的示例中,我们将经常展示 sessionmaker 在我们实际调用 Session 的行的正上方创建。但这只是为了例子!实际上,会话创建者将位于模块级别的某个位置。对实例化 Session 的调用 将放置在应用程序中的 database 对话开始了。


何时构造 Session,何时提交,何时关闭它?


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


每当 Session 用于与数据库通信时,它都会在开始通信时立即开始数据库事务。此事务将一直进行中,直到会话 已回滚、提交或关闭。 如果再次使用 Session 会话,则在上一个事务结束之后开始一个新的事务;由此可见,Session 能够在许多事务中具有生命周期,但只有 一次一个。 我们将这两个概念称为交易范围session 范围


通常,确定开始和结束 Session 范围的最佳点并不难,尽管可能的各种应用程序体系结构可能会带来具有挑战性的情况。


一些示例场景包括:


  • Web 应用程序。在这种情况下,最好使用正在使用的 Web 框架提供的 SQLAlchemy 集成。否则,基本模式是在 Web 请求开始时创建一个 Session,在 执行 POST、PUT 或 DELETE,然后关闭会话的 Web 请求 在 Web 请求的末尾。 设置 Session.expire_on_commit False,以便对来自视图层中 Session 的对象的后续访问不需要发出新的 SQL 查询来刷新对象(如果事务已经提交)。


  • 从子 fork 生成的后台守护进程希望在每个子进程本地创建一个 Session,在 fork 正在处理的 “job” 的生命周期中与该 Session 一起工作,然后在 job 完成时将其拆除。


  • 对于命令行脚本,应用程序将创建一个全局 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: keyword) 以便自动管理 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 是缓存吗?


哎呀...不。它在某种程度上用作缓存,因为它实现了 身份映射模式,并存储与其主键键相关的对象。 但是,它不执行任何类型的查询缓存。这意味着,如果你说 session.scalars(select(Foo).filter_by(name='bar')) ,即使 Foo(name='bar') 就在那里,在身份映射中,会话对此一无所知。 它必须向数据库发出 SQL,取回行,然后在 看到该行中的主键,那么它可以在本地身份中查找 map 并查看对象是否已存在。只有当你说 query.get({some primary key})Session 不必发出查询。


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


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


如何获取某个对象的 Session


使用 Session 上可用的 Session.object_session() 类方法:

session = Session.object_session(someobject)


还可以使用较新的 Runtime Inspection API 系统:

from sqlalchemy import inspect

session = inspect(someobject).session


Session 线程安全吗?在并发任务中共享 AsyncSession 是否安全?


Session 是一个可变的、有状态的对象,它表示单个 数据库事务。因此,Session 的实例不能 在并发线程或 asyncio 任务之间共享,无需小心 同步Session 旨在用于 non-concurrent 方式,即 Session 的特定实例 一次只能在一个线程或任务中使用。


当使用 SQLAlchemy 的 asyncio 扩展,这个对象只是 Session 之上的一个瘦代理,同样的规则也适用;它是一个 unsynchronized、mutable、stateful 对象,因此同时在多个 asyncio 任务中使用单个 AsyncSession 实例是安全的。


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


事务中的数据库连接也是一个有状态对象,它是 旨在以非并发、顺序的方式进行作。命令 按顺序在连接上发出,由数据库处理 server 的 SERVER 中执行它们。 由于 Session 在此连接上发出命令并接收结果,Session 本身正在通过内部状态更改进行转换,这些更改与此连接上存在的命令和数据的状态一致;状态,包括事务是否开始、提交或回滚,哪些 SAVEPOINT(如果有)正在发挥作用,以及单个数据库行的状态与本地 ORM 映射对象的细粒度同步。


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


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


确保此用途的最佳方法是使用标准上下文管理器 pattern 在本地的顶级 Python 函数中,该 位于线程或任务内部,这将确保 SessionAsyncSession 在本地范围内维护。


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