会话基础知识

Session Basics

会话有什么用?

What does the Session do ?

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

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

Session 维护的 ORM 对象是 instrumented 的,这样每当在 Python 程序中修改属性或集合时,都会生成一个变更事件,该事件由 Session 记录。每当即将查询数据库或即将提交事务时,Session 会首先将内存中存储的所有待处理更改 刷新 到数据库。这称为 unit of work 模式。

使用 Session 时,考虑它维护的 ORM 映射对象作为 代理对象 到数据库行是有用的,这些对象是本地于 Session 持有的事务的。为了保持对象状态与数据库中的实际内容匹配,有多种事件会导致对象重新访问数据库以保持同步。可以将对象从 Session 中“分离”并继续使用它们,尽管这种做法有其警告。通常情况下,当你想再次使用它们时,你会将分离的对象重新关联到另一个 Session,以便它们可以恢复表示数据库状态的正常任务。

In the most general sense, the Session establishes all conversations with the database and represents a “holding zone” for all the objects which you’ve loaded or associated with it during its lifespan. It provides the interface where SELECT and other queries are made that will return and modify ORM-mapped objects. The ORM objects themselves are maintained inside the Session, inside a structure called the identity map - a data structure that maintains unique copies of each object, where “unique” means “only one object with a particular primary key”.

The Session in its most common pattern of use begins in a mostly stateless form. Once queries are issued or other objects are persisted with it, it requests a connection resource from an Engine that is associated with the Session, and then establishes a transaction on that connection. This transaction remains in effect until the Session is instructed to commit or roll back the transaction. When the transaction ends, the connection resource associated with the Engine is released to the connection pool managed by the engine. A new transaction then starts with a new connection checkout.

The ORM objects maintained by a Session are instrumented such that whenever an attribute or a collection is modified in the Python program, a change event is generated which is recorded by the Session. Whenever the database is about to be queried, or when the transaction is about to be committed, the Session first flushes all pending changes stored in memory to the database. This is known as the unit of work pattern.

When using a Session, it’s useful to consider the ORM mapped objects that it maintains as proxy objects to database rows, which are local to the transaction being held by the Session. In order to maintain the state on the objects as matching what’s actually in the database, there are a variety of events that will cause objects to re-access the database in order to keep synchronized. It is possible to “detach” objects from a Session, and to continue using them, though this practice has its caveats. It’s intended that usually, you’d re-associate detached objects with another Session when you want to work with them again, so that they can resume their normal task of representing database state.

使用会话的基础知识

Basics of Using a Session

这里介绍了最基本的 Session 使用模式。

The most basic Session use patterns are presented here.

打开和关闭会话

Opening and Closing a Session

Session 可以单独构造,也可以通过 sessionmaker 类来构造。它通常在初始化时传入一个 Engine 作为连接资源的来源。一个典型的用法如下所示:

from sqlalchemy import create_engine
from sqlalchemy.orm import Session

# 一个 Engine,Session 将使用它进行连接
# 资源管理
engine = create_engine("postgresql+psycopg2://scott:tiger@localhost/")

# 创建 session 并添加对象
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`(无论是显式调用还是在使用上下文管理器时隐式调用)之后,所有与 :class:().Session` 相关联的对象都会被 expired,也就是说它们的内容会被清除,并在下一次事务中重新加载。如果这些对象被 detached,它们将失去功能,直到重新与一个新的 Session 相关联,除非使用了 Session.expire_on_commit 参数来禁用这一行为。详见 提交 一节。

The Session may be constructed on its own or by using the sessionmaker class. It typically is passed a single Engine as a source of connectivity up front. A typical use may look like:

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()

Above, the Session is instantiated with an Engine associated with a particular database URL. It is then used in a Python context manager (i.e. with: statement) so that it is automatically closed at the end of the block; this is equivalent to calling the Session.close() method.

The call to Session.commit() is optional, and is only needed if the work we’ve done with the Session includes new data to be persisted to the database. If we were only issuing SELECT calls and did not need to write any changes, then the call to Session.commit() would be unnecessary.

备注

Note that after Session.commit() is called, either explicitly or when using a context manager, all objects associated with the Session are expired, meaning their contents are erased to be re-loaded within the next transaction. If these objects are instead detached, they will be non-functional until re-associated with a new Session, unless the Session.expire_on_commit parameter is used to disable this behavior. See the section 提交 for more detail.

构建开始/提交/回滚块

Framing out a begin / commit / rollback block

我们还可以在上下文管理器中包裹对 Session.commit() 的调用,以及事务的整体“框架”,适用于那些需要将数据提交到数据库的场景。所谓“框架”是指:如果所有操作都成功执行,则调用 Session.commit() 方法;如果抛出任何异常,则调用 Session.rollback() 方法以立即回滚事务,然后将异常继续抛出。在 Python 中,这通常使用 try: / except: / else: 结构表达,如下所示:

# 展示上下文管理器内部实际做的事情的详细版本
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 对象来实现,该对象提供了相同操作序列的上下文管理器接口:

# 创建 session 并添加对象
with Session(engine) as session:
    with session.begin():
        session.add(some_object)
        session.add(some_other_object)
    # 如果没有异常,内部上下文会调用 session.commit()
# 外部上下文会调用 session.close()

更进一步地,也可以将这两个上下文合并:

# 创建 session 并添加对象
with Session(engine) as session, session.begin():
    session.add(some_object)
    session.add(some_other_object)
# 如果没有异常,内部上下文会调用 session.commit()
# 外部上下文会调用 session.close()

We may also enclose the Session.commit() call and the overall “framing” of the transaction within a context manager for those cases where we will be committing data to the database. By “framing” we mean that if all operations succeed, the Session.commit() method will be called, but if any exceptions are raised, the Session.rollback() method will be called so that the transaction is rolled back immediately, before propagating the exception outward. In Python this is most fundamentally expressed using a try: / except: / else: block such as:

# 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()

The long-form sequence of operations illustrated above can be achieved more succinctly by making use of the SessionTransaction object returned by the Session.begin() method, which provides a context manager interface for the same sequence of operations:

# 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()

More succinctly, the two contexts may be combined:

# 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

Using a sessionmaker

sessionmaker 的目的是提供一个具有固定配置的 Session 对象工厂。由于应用程序通常会在模块作用域中定义一个 Engine 对象,sessionmaker 可以提供一个基于该 engine 构造 Session 对象的工厂:

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

# 一个 Engine,Session 将使用它进行连接资源的管理,通常在模块作用域中定义
engine = create_engine("postgresql+psycopg2://scott:tiger@localhost/")

# 一个 sessionmaker(),通常也在与 engine 相同的作用域中定义
Session = sessionmaker(engine)

# 现在我们可以直接构造 Session(),无需每次都传入 engine
with Session() as session:
    session.add(some_object)
    session.add(some_other_object)
    session.commit()
# 自动关闭 session

sessionmaker 类似于 Engine,都是在模块级作用域中作为函数级连接 / 会话的工厂使用。因此它也拥有自己的 sessionmaker.begin() 方法,类似于 Engine.begin(),该方法会返回一个 Session 对象,并同时管理一个 begin/commit/rollback 块:

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

# 一个 Engine,Session 将使用它进行连接资源的管理
engine = create_engine("postgresql+psycopg2://scott:tiger@localhost/")

# 一个 sessionmaker(),通常也在与 engine 相同的作用域中定义
Session = sessionmaker(engine)

# 我们现在可以构造一个 Session() 并一次性包含 begin()/commit()/rollback()
with Session.begin() as session:
    session.add(some_object)
    session.add(some_other_object)
# 提交事务并关闭 session

在上述代码中,当 with: 块结束时,Session 会自动提交其事务,并关闭该 Session 实例。

在实际应用中,sessionmaker 工厂应与通过 create_engine() 创建的 Engine 对象处于相同作用域,通常是在模块级或全局作用域。由于它们都是工厂对象,因此可以同时被多个函数和线程安全地使用。

The purpose of sessionmaker is to provide a factory for Session objects with a fixed configuration. As it is typical that an application will have an Engine object in module scope, the sessionmaker can provide a factory for Session objects that are constructed against this engine:

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

The sessionmaker is analogous to the Engine as a module-level factory for function-level sessions / connections. As such it also has its own sessionmaker.begin() method, analogous to Engine.begin(), which returns a Session object and also maintains a begin/commit/rollback block:

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

Where above, the Session will both have its transaction committed as well as that the Session will be closed, when the above with: block ends.

When you write your application, the sessionmaker factory should be scoped the same as the Engine object created by create_engine(), which is typically at module-level or global scope. As these objects are both factories, they can be used by any number of functions and threads simultaneously.

查询

Querying

进行查询的主要方式是使用 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:
    # 查询 ``User`` 对象
    statement = select(User).filter_by(name="ed")

    # ``User`` 对象列表
    user_obj = session.scalars(statement).all()

    # 查询单独的列
    statement = select(User.name, User.fullname)

    # Row 对象的列表
    rows = session.execute(statement).all()

在 2.0 版本发生变更: “2.0” 风格的查询方式现在是标准方式。关于从 1.x 系列迁移的说明请参阅 2.0 Migration - ORM Usage

The primary means of querying is to make use of the select() construct to create a Select object, which is then executed to return a result using methods such as Session.execute() and Session.scalars(). Results are then returned in terms of Result objects, including sub-variants such as ScalarResult.

A complete guide to SQLAlchemy ORM querying can be found at ORM 查询指南. Some brief examples follow:

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” style querying is now standard. See 2.0 Migration - ORM Usage for migration notes from the 1.x series.

添加新项目或现有项目

Adding New or Existing Items

Session.add() 用于将实例放入会话中。对于 :term:`transient`(即全新创建的)实例,这将在下一个 flush 时触发 INSERT 操作。对于已经是 :term:`persistent`(即由当前会话加载的)实例,它们已在会话中,无需再添加。对于 :term:`detached`(即已从会话中移除的)实例,可以使用此方法重新关联到会话中:

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

session.commit()  # 将更改写入数据库

要一次性将多个对象添加到会话中,可以使用 Session.add_all() 方法:

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

Session.add() 操作会 级联传播(cascade),沿着 save-update 级联路径传播。详细内容见章节 级联

Session.add() is used to place instances in the session. For transient (i.e. brand new) instances, this will have the effect of an INSERT taking place for those instances upon the next flush. For instances which are persistent (i.e. were loaded by this session), they are already present and do not need to be added. Instances which are detached (i.e. have been removed from a session) may be re-associated with a session using this method:

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

session.commit()  # write changes to the database

To add a list of items to the session at once, use Session.add_all():

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

The Session.add() operation cascades along the save-update cascade. For more details see the section 级联.

删除

Deleting

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

# 标记两个对象为待删除
session.delete(obj1)
session.delete(obj2)

# 提交(或 flush)
session.commit()

Session.delete() 会标记某个对象为删除状态,从而会针对每个受影响的主键发出 DELETE 语句。在删除操作 flush 之前,被标记为删除的对象会出现在 Session.deleted 集合中。删除执行后,这些对象会从 Session 中清除,在事务提交后这一清除操作将变为永久性。

关于 Session.delete() 操作存在一些重要行为,尤其是与该对象存在关系的其他对象或集合的处理方式。更多内容可见 级联 章节,但一般规则如下:

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

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

  • 若是通过 relationship.secondary 参数连接的 “多对多” 中间表,始终 会在被引用对象被删除时清除相关行。

  • 当关联对象具有指向被删除对象的外键约束,且这些相关集合当前未加载到内存中,工作单元(unit of work)会发出 SELECT 查询以加载所有相关行,从而获取其主键用于执行 UPDATE 或 DELETE 操作。这样,即使在 Core 层的 ForeignKeyConstraint 中配置了 ON DELETE CASCADE,ORM 也能在无需进一步指令的情况下完成相同的功能。

  • 可通过设置 relationship.passive_deletes 参数来优化此行为,并更自然地依赖 ON DELETE CASCADE。当设置为 True 时,不再执行上述 SELECT 查询,但本地存在的行仍会被显式地 SET NULL 或 DELETE。若将 relationship.passive_deletes 设置为字符串 "all",则会禁用 所有 与关联对象相关的更新/删除。

  • 当某对象被标记为删除并实际执行 DELETE 操作时,该对象 不会 自动从引用它的集合或对象中移除。当 Session 过期后,这些集合可能会重新加载,此时该对象将不再存在。然而,更推荐的做法是,不使用 Session.delete() 删除这些对象,而是先将对象从集合中移除,再使用 删除-孤立 实现因集合变更而触发的删除行为。相关示例见 删除注意事项 - 删除从集合和标量关系中引用的对象

参见

删除 - 描述 “删除级联”,当主对象被删除时标记其关联对象也应被删除。

删除-孤立 - 描述 “孤儿删除级联”,当关联对象从主对象中解除关联时被删除。

删除注意事项 - 删除从集合和标量关系中引用的对象 - 关于 Session.delete() 如何与关系刷新机制交互的背景说明。

The Session.delete() method places an instance into the Session’s list of objects to be marked as deleted:

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

# commit (or flush)
session.commit()

Session.delete() marks an object for deletion, which will result in a DELETE statement emitted for each primary key affected. Before the pending deletes are flushed, objects marked by “delete” are present in the Session.deleted collection. After the DELETE, they are expunged from the Session, which becomes permanent after the transaction is committed.

There are various important behaviors related to the Session.delete() operation, particularly in how relationships to other objects and collections are handled. There’s more information on how this works in the section 级联, but in general the rules are:

  • Rows that correspond to mapped objects that are related to a deleted object via the relationship() directive are not deleted by default. If those objects have a foreign key constraint back to the row being deleted, those columns are set to NULL. This will cause a constraint violation if the columns are non-nullable.

  • To change the “SET NULL” into a DELETE of a related object’s row, use the 删除 cascade on the relationship().

  • Rows that are in tables linked as “many-to-many” tables, via the relationship.secondary parameter, are deleted in all cases when the object they refer to is deleted.

  • When related objects include a foreign key constraint back to the object being deleted, and the related collections to which they belong are not currently loaded into memory, the unit of work will emit a SELECT to fetch all related rows, so that their primary key values can be used to emit either UPDATE or DELETE statements on those related rows. In this way, the ORM without further instruction will perform the function of ON DELETE CASCADE, even if this is configured on Core ForeignKeyConstraint objects.

  • The relationship.passive_deletes parameter can be used to tune this behavior and rely upon “ON DELETE CASCADE” more naturally; when set to True, this SELECT operation will no longer take place, however rows that are locally present will still be subject to explicit SET NULL or DELETE. Setting relationship.passive_deletes to the string "all" will disable all related object update/delete.

  • When the DELETE occurs for an object marked for deletion, the object is not automatically removed from collections or object references that refer to it. When the Session is expired, these collections may be loaded again so that the object is no longer present. However, it is preferable that instead of using Session.delete() for these objects, the object should instead be removed from its collection and then 删除-孤立 should be used so that it is deleted as a secondary effect of that collection removal. See the section 删除注意事项 - 删除从集合和标量关系中引用的对象 for an example of this.

参见

删除 - describes “delete cascade”, which marks related objects for deletion when a lead object is deleted.

删除-孤立 - describes “delete orphan cascade”, which marks related objects for deletion when they are de-associated from their lead object.

删除注意事项 - 删除从集合和标量关系中引用的对象 - important background on Session.delete() as involves relationships being refreshed in memory.

刷新

Flushing

当使用 Session 的默认配置时,flush(刷新)步骤几乎总是自动完成的。具体来说,当因调用 Query 或术语中所说的 2.0-style 方法 Session.execute() 而将要发出某个 SQL 语句时,flush 会在其之前发生;同样,在调用 Session.commit() 以提交事务之前也会自动发生 flush;另外,在使用 Session.begin_nested() 发出 SAVEPOINT 之前,也会发生 flush。

你可以在任何时候手动调用 Session.flush() 来强制进行一次刷新操作:

session.flush()

某些方法范围内自动发生的刷新称为 自动刷新(autoflush)。Autoflush 是一个可配置的自动刷新机制,通常发生在以下方法开始执行时:

  • 调用 Session.execute() 以及其他执行 SQL 的方法,并且目标是启用了 ORM 的 SQL 构造(如指向 ORM 实体或 ORM 映射属性的 select() 对象)时;

  • 通过 Query 发送 SQL 到数据库时;

  • 在调用 Session.merge() 方法、并查询数据库之前;

  • 对象被 刷新 时;

  • 对尚未加载的对象属性触发 ORM 的 :term:`lazy load`(延迟加载)操作时。

同时,也有一些刷新是 无条件发生 的,这些情况包括关键事务边界内的操作:

Autoflush 行为在上述情形中可以通过在构造 Sessionsessionmaker 时传入 Session.autoflush=False 来禁用:

Session = sessionmaker(autoflush=False)

此外,还可以在使用某个 Session 时临时禁用 autoflush,通过使用 Session.no_autoflush 上下文管理器实现:

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

重申一遍: 无论设置是否启用 autoflush,在调用诸如 Session.commit()Session.begin_nested() 等事务性方法时,如果会话中仍存在待处理更改,flush 始终会发生

由于 Session 仅会在 DBAPI 事务上下文中对数据库执行 SQL,所有的 “flush” 操作也都必须在数据库事务中执行(具体受数据库事务的 隔离级别 影响),前提是 DBAPI 并未启用 驱动级自动提交 模式。这意味着,如果数据库连接在其事务设置中保证了 原子性,那么 flush 中的任意 DML 语句失败时,整个操作将会被回滚。

当 flush 过程中发生失败时,为了继续使用当前 Session,必须显式调用 Session.rollback() 来回滚,即便底层事务已经被回滚(即使数据库驱动启用了驱动级自动提交)。这样做的目的是维护所谓“子事务(subtransaction)”的嵌套模式一致性。FAQ 部分 “由于刷新期间出现先前的异常,此 Session 的事务已被回滚。”(或类似) 中对此行为有更详细的说明。

When the Session is used with its default configuration, the flush step is nearly always done transparently. Specifically, the flush occurs before any individual SQL statement is issued as a result of a Query or a 2.0-style Session.execute() call, as well as within the Session.commit() call before the transaction is committed. It also occurs before a SAVEPOINT is issued when Session.begin_nested() is used.

A Session flush can be forced at any time by calling the Session.flush() method:

session.flush()

The flush which occurs automatically within the scope of certain methods is known as autoflush. Autoflush is defined as a configurable, automatic flush call which occurs at the beginning of methods including:

  • Session.execute() and other SQL-executing methods, when used against ORM-enabled SQL constructs, such as select() objects that refer to ORM entities and/or ORM-mapped attributes

  • When a Query is invoked to send SQL to the database

  • Within the Session.merge() method before querying the database

  • When objects are refreshed

  • When ORM lazy load operations occur against unloaded object attributes.

There are also points at which flushes occur unconditionally; these points are within key transactional boundaries which include:

The autoflush behavior, as applied to the previous list of items, can be disabled by constructing a Session or sessionmaker passing the Session.autoflush parameter as False:

Session = sessionmaker(autoflush=False)

Additionally, autoflush can be temporarily disabled within the flow of using a Session using the Session.no_autoflush context manager:

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

To reiterate: The flush process always occurs when transactional methods such as Session.commit() and Session.begin_nested() are called, regardless of any “autoflush” settings, when the Session has remaining pending changes to process.

As the Session only invokes SQL to the database within the context of a DBAPI transaction, all “flush” operations themselves only occur within a database transaction (subject to the isolation level of the database transaction), provided that the DBAPI is not in driver level autocommit mode. This means that assuming the database connection is providing for atomicity within its transactional settings, if any individual DML statement inside the flush fails, the entire operation will be rolled back.

When a failure occurs within a flush, in order to continue using that same Session, an explicit call to Session.rollback() is required after a flush fails, even though the underlying transaction will have been rolled back already (even if the database driver is technically in driver-level autocommit mode). This is so that the overall nesting pattern of so-called “subtransactions” is consistently maintained. The FAQ section “由于刷新期间出现先前的异常,此 Session 的事务已被回滚。”(或类似) contains a more detailed description of this behavior.

通过主键获取

Get by Primary Key

由于 Session 使用了一个 标识图(identity map) 来通过主键引用当前内存中的对象,提供了 Session.get() 方法用于根据主键定位对象。该方法首先会在当前标识图中查找,如果未命中才会查询数据库。例如,查找主键为 (5,)User 实体:

my_user = session.get(User, 5)

Session.get() 同时也支持复合主键的形式,可以使用元组或字典传入,并支持附加参数以控制加载方式和执行选项。完整参数说明详见 Session.get()

参见

Session.get()

As the Session makes use of an identity map which refers to current in-memory objects by primary key, the Session.get() method is provided as a means of locating objects by primary key, first looking within the current identity map and then querying the database for non present values. Such as, to locate a User entity with primary key identity (5, ):

my_user = session.get(User, 5)

The Session.get() also includes calling forms for composite primary key values, which may be passed as tuples or dictionaries, as well as additional parameters which allow for specific loader and execution options. See Session.get() for the complete parameter list.

参见

Session.get()

过期/刷新

Expiring / Refreshing

在使用 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() 方法会清除对象中选定或全部属性的内容,使得当这些属性下次被访问时,

    会自动从数据库中加载,例如通过 延迟加载(lazy loading) 机制:

    session.expire(u1) u1.some_attribute # <– 访问时将从事务中延迟加载

  • `refresh()` 方法 - 与之密切相关的是 Session.refresh() 方法,它执行与 expire() 相同的操作,但会立即发出一个或多个 SQL 查询,从而实际刷新对象的内容:

    session.refresh(u1)  # <-- 立即发出 SQL 查询
    u1.some_attribute  # <-- 从事务中刷新后的值
  • `populate_existing()` 方法或执行选项 - 现在作为一个执行选项记录在 填充现有 中;在旧版本中,它是 Query 对象上的 Query.populate_existing() 方法。该操作表示从查询中返回的对象应无条件地使用数据库中的内容重新填充:

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

关于 refresh / expire 概念的更多讨论,请参见 刷新/过期

An important consideration that will often come up when using the Session is that of dealing with the state that is present on objects that have been loaded from the database, in terms of keeping them synchronized with the current state of the transaction. The SQLAlchemy ORM is based around the concept of an identity map such that when an object is “loaded” from a SQL query, there will be a unique Python object instance maintained corresponding to a particular database identity. This means if we emit two separate queries, each for the same row, and get a mapped object back, the two queries will have returned the same Python object:

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

Following from this, when the ORM gets rows back from a query, it will skip the population of attributes for an object that’s already loaded. The design assumption here is to assume a transaction that’s perfectly isolated, and then to the degree that the transaction isn’t isolated, the application can take steps on an as-needed basis to refresh objects from the database transaction. The FAQ entry at 我正在使用 Session 重新加载数据,但它没有看到我在其他地方提交的更改 discusses this concept in more detail.

When an ORM mapped object is loaded into memory, there are three general ways to refresh its contents with new data from the current transaction:

  • the expire() method - the Session.expire() method will erase the contents of selected or all attributes of an object, such that they will be loaded from the database when they are next accessed, e.g. using a lazy loading pattern:

    session.expire(u1)
    u1.some_attribute  # <-- lazy loads from the transaction
  • the refresh() method - closely related is the Session.refresh() method, which does everything the Session.expire() method does but also emits one or more SQL queries immediately to actually refresh the contents of the object:

    session.refresh(u1)  # <-- emits a SQL query
    u1.some_attribute  # <-- is refreshed from the transaction
    
    ..
  • the populate_existing() method or execution option - This is now an execution option documented at 填充现有; in legacy form it’s found on the Query object as the Query.populate_existing() method. This operation in either form indicates that objects being returned from a query should be unconditionally re-populated from their contents in the database:

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

Further discussion on the refresh / expire concept can be found at 刷新/过期.

使用任意 WHERE 子句进行更新和删除

UPDATE and DELETE with arbitrary WHERE clause

SQLAlchemy 2.0 引入了更强大的能力,支持多种基于 ORM 的 INSERT、UPDATE 和 DELETE 语句的发出方式。详见文档 启用 ORM 的 INSERT、UPDATE 和 DELETE 语句

SQLAlchemy 2.0 includes enhanced capabilities for emitting several varieties of ORM-enabled INSERT, UPDATE and DELETE statements. See the document at 启用 ORM 的 INSERT、UPDATE 和 DELETE 语句 for documentation.

自动开始

Auto Begin

Session 对象具有一种称为 autobegin (自动开始)的行为。这意味着,一旦对 Session 执行了任何操作——无论是涉及对象状态变化的内部状态修改,还是需要数据库连接的操作——该 Session 会自动将自己视为处于“事务性”状态中。

Session 首次构造时,其内部并不包含任何事务状态。事务状态会在以下情况中自动开始:调用了例如 Session.add()Session.execute() 等方法,执行了某个 Query 以返回结果(该操作最终也会调用 Session.execute()),或对某个 :term:`persistent`(持久化)对象的属性进行了修改。

可以通过访问 Session.in_transaction() 方法来检查当前是否处于事务状态,该方法返回布尔值 TrueFalse,指示 “autobegin” 步骤是否已经发生。虽然通常不需要,但也可以通过 Session.get_transaction() 方法获取表示当前事务状态的实际 SessionTransaction 对象。

此外,也可以显式地通过调用 Session.begin() 方法来开始事务状态。调用此方法时,Session 会无条件地进入“事务性”状态。Session.begin() 可以作为上下文管理器使用,详细说明请见 构建开始/提交/回滚块

The Session object features a behavior known as autobegin. This indicates that the Session will internally consider itself to be in a “transactional” state as soon as any work is performed with the Session, either involving modifications to the internal state of the Session with regards to object state changes, or with operations that require database connectivity.

When the Session is first constructed, there’s no transactional state present. The transactional state is begun automatically, when a method such as Session.add() or Session.execute() is invoked, or similarly if a Query is executed to return results (which ultimately uses Session.execute()), or if an attribute is modified on a persistent object.

The transactional state can be checked by accessing the Session.in_transaction() method, which returns True or False indicating if the “autobegin” step has proceeded. While not normally needed, the Session.get_transaction() method will return the actual SessionTransaction object that represents this transactional state.

The transactional state of the Session may also be started explicitly, by invoking the Session.begin() method. When this method is called, the Session is placed into the “transactional” state unconditionally. Session.begin() may be used as a context manager as described at 构建开始/提交/回滚块.

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

Disabling Autobegin to Prevent Implicit Transactions

可以通过将参数 Session.autobegin 设置为 False 来禁用 “autobegin” 行为。设置此参数后,Session 将要求用户显式调用 Session.begin() 方法以启动事务。在创建会话对象之后,或调用了 Session.rollback()Session.commit()Session.close() 方法之后,该 Session 将不会自动开启新事务,如果之后尝试使用该对象而未显式调用 Session.begin(),将会抛出错误:

with Session(engine, autobegin=False) as session:
    session.begin()  # <-- 必须调用,否则下次操作将抛出 InvalidRequestError

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

    session.begin()  # <-- 必须调用,否则下次操作将抛出 InvalidRequestError

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

在 2.0 版本加入: 新增 Session.autobegin 参数,允许禁用 “autobegin” 行为

The “autobegin” behavior may be disabled using the Session.autobegin parameter set to False. By using this parameter, a Session will require that the Session.begin() method is called explicitly. Upon construction, as well as after any of the Session.rollback(), Session.commit(), or Session.close() methods are called, the Session won’t implicitly begin any new transactions and will raise an error if an attempt to use the Session is made without first calling 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 版本加入: Added Session.autobegin, allowing “autobegin” behavior to be disabled

提交

Committing

Session.commit() 用于提交当前事务。其核心行为是在所有当前处于事务状态的数据库连接上发出 COMMIT 命令;从 DBAPI 的角度来看,这意味着在每个 DBAPI 连接上调用 connection.commit() 方法。

如果 Session 当前没有处于事务状态(表示自上次调用 Session.commit() 后未执行任何操作),该方法将启动并提交一个仅限内部使用的“逻辑事务”。此事务通常不会对数据库产生实际影响,除非检测到有待刷新的变更,但仍会触发事件处理器与对象过期机制。

Session.commit() 操作会在对相关数据库连接发出 COMMIT 前 无条件 调用 Session.flush() 方法。如果没有检测到待处理的变更,则不会向数据库发送任何 SQL。这一行为不可配置,也不会受到 Session.autoflush 参数的影响。

随后,假设 Session 绑定到了某个 Engine,则 Session.commit() 会对已开始的实际数据库事务执行 COMMIT。提交完成后,与该事务关联的 Connection 对象会被关闭,其底层的 DBAPI 连接也会被 释放 回连接池,该连接池隶属于绑定的 Engine

对于绑定到多个引擎的 _orm.Session`(例如见 :ref:`分区策略),提交的逻辑事务中每个涉及的 Engine / Connection 都将进行上述相同的 COMMIT 操作。除非启用了 两阶段提交,否则这些数据库事务之间是 不协调 的。

也可以将 Session 绑定到某个 Connection 上来使用其他的连接交互模式;在这种模式下,假定存在一个 外部管理的事务,此时将不会自动执行 COMMIT。详见 将会话加入外部事务(例如测试套件) 章节了解此模式的背景。

最后,在事务结束后,Session 中的所有对象都将被 过期。这样可以确保当这些实例再次被访问(无论是通过属性访问还是出现在 SELECT 查询结果中)时,它们将获得最新状态。此行为可通过 Session.expire_on_commit 标志进行控制;若该行为不希望发生,可将其设置为 False

参见

自动开始

如果你还需要我继续翻译其他部分,或者想要对这段内容进行更深入的解释,也可以随时告诉我!

Session.commit() is used to commit the current transaction. At its core this indicates that it emits COMMIT on all current database connections that have a transaction in progress; from a DBAPI perspective this means the connection.commit() DBAPI method is invoked on each DBAPI connection.

When there is no transaction in place for the Session, indicating that no operations were invoked on this Session since the previous call to Session.commit(), the method will begin and commit an internal-only “logical” transaction, that does not normally affect the database unless pending flush changes were detected, but will still invoke event handlers and object expiration rules.

The Session.commit() operation unconditionally issues Session.flush() before emitting COMMIT on relevant database connections. If no pending changes are detected, then no SQL is emitted to the database. This behavior is not configurable and is not affected by the Session.autoflush parameter.

Subsequent to that, assuming the Session is bound to an Engine, Session.commit() will then COMMIT the actual database transaction that is in place, if one was started. After the commit, the Connection object associated with that transaction is closed, causing its underlying DBAPI connection to be released back to the connection pool associated with the Engine to which the Session is bound.

For a Session that’s bound to multiple engines (e.g. as described at Partitioning Strategies), the same COMMIT steps will proceed for each Engine / Connection that is in play within the “logical” transaction being committed. These database transactions are uncoordinated with each other unless two-phase features are enabled.

Other connection-interaction patterns are available as well, by binding the Session to a Connection directly; in this case, it’s assumed that an externally-managed transaction is present, and a real COMMIT will not be emitted automatically in this case; see the section 将会话加入外部事务(例如测试套件) for background on this pattern.

Finally, all objects within the Session are expired as the transaction is closed out. This is so that when the instances are next accessed, either through attribute access or by them being present in the result of a SELECT, they receive the most recent state. This behavior may be controlled by the Session.expire_on_commit flag, which may be set to False when this behavior is undesirable.

参见

自动开始

回滚

Rolling Back

Session.rollback() 方法用于回滚当前事务(若存在)。若当前没有事务,该方法将 静默通过,不会引发异常。

在默认配置的会话中,若事务是通过 自动开始 或显式调用 Session.begin() 方法开启的,在回滚之后会处于如下状态:

  • 数据库事务会被回滚。对于绑定到单个 EngineSession,这意味着最多只会对一个 Connection 发出 ROLLBACK。若绑定到多个 Engine,则所有被检出的 Connection 都会进行 ROLLBACK。

  • 数据库连接被 释放。其行为与 提交 中描述的连接释放机制一致:从 Engine 获取的 Connection 被关闭,从而将底层 DBAPI 连接释放回该引擎的连接池。若之后开始新的事务,将重新从引擎中检出连接。

  • 若会话绑定的是一个 Connection`(参见 :ref:`session_external_transaction),其回滚行为将根据 Session.join_transaction_mode 参数决定,可能涉及回滚保存点(savepoints)或发出实际的 ROLLBACK。

  • 所有在事务期间处于 pending 状态(即刚添加到会话中的)对象将被驱逐(expunged),对应的 INSERT 操作也被回滚,但其属性状态仍然保留。

  • 所有在事务期间被标记为 deleted 的对象将被提升回 persistent 状态,表示其 DELETE 操作被回滚。注意,如果该对象最初在事务中是 pending 状态,那么“pending”操作优先生效。

  • 所有未被驱逐的对象都将被完全过期——这一点 不受 Session.expire_on_commit 设置的影响。

理解上述状态后,Session 可在回滚后安全地继续使用。

在 1.4 版本发生变更: Session 现在具有延迟“开始事务”的行为,详见 自动开始。如果事务未开始,则调用 Session.commit()Session.rollback() 不会有任何效果。在 1.4 之前版本中,由于非自动提交(autocommit)模式下总是隐式存在事务,因此不会出现该行为。

Session.flush() 失败(通常因为主键、外键或“非空”约束违反等原因)时,会自动执行 ROLLBACK(目前 flush 无法在部分失败后继续执行)。但此时 Session 会进入一种称为“非活动”(inactive)的状态,此时调用方必须 显式调用 Session.rollback() 方法,使会话恢复为可用状态(也可以直接关闭并丢弃该会话)。详见 FAQ 条目 “由于刷新期间出现先前的异常,此 Session 的事务已被回滚。”(或类似) 获取更多说明。

参见

自动开始

Session.rollback() rolls back the current transaction, if any. When there is no transaction in place, the method passes silently.

With a default configured session, the post-rollback state of the session, subsequent to a transaction having been begun either via autobegin or by calling the Session.begin() method explicitly, is as follows:

  • Database transactions are rolled back. For a Session bound to a single Engine, this means ROLLBACK is emitted for at most a single Connection that’s currently in use. For Session objects bound to multiple Engine objects, ROLLBACK is emitted for all Connection objects that were checked out.

  • Database connections are released. This follows the same connection-related behavior noted in 提交, where Connection objects obtained from Engine objects are closed, causing the DBAPI connections to be released to the connection pool within the Engine. New connections are checked out from the Engine if and when a new transaction begins.

  • For a Session that’s bound directly to a Connection as described at 将会话加入外部事务(例如测试套件), rollback behavior on this Connection would follow the behavior specified by the Session.join_transaction_mode parameter, which could involve rolling back savepoints or emitting a real ROLLBACK.

  • Objects which were initially in the pending state when they were added to the Session within the lifespan of the transaction are expunged, corresponding to their INSERT statement being rolled back. The state of their attributes remains unchanged.

  • Objects which were marked as deleted within the lifespan of the transaction are promoted back to the persistent state, corresponding to their DELETE statement being rolled back. Note that if those objects were first pending within the transaction, that operation takes precedence instead.

  • All objects not expunged are fully expired - this is regardless of the Session.expire_on_commit setting.

With that state understood, the Session may safely continue usage after a rollback occurs.

在 1.4 版本发生变更: The Session object now features deferred “begin” behavior, as described in autobegin. If no transaction is begun, methods like Session.commit() and Session.rollback() have no effect. This behavior would not have been observed prior to 1.4 as under non-autocommit mode, a transaction would always be implicitly present.

When a Session.flush() fails, typically for reasons like primary key, foreign key, or “not nullable” constraint violations, a ROLLBACK is issued automatically (it’s currently not possible for a flush to continue after a partial failure). However, the Session goes into a state known as “inactive” at this point, and the calling application must always call the Session.rollback() method explicitly so that the Session can go back into a usable state (it can also be simply closed and discarded). See the FAQ entry at “由于刷新期间出现先前的异常,此 Session 的事务已被回滚。”(或类似) for further discussion.

参见

自动开始

关闭

Closing

Session.close() 方法会调用 Session.expunge_all(),将所有 ORM 映射的对象从会话中移除,并且 释放 所绑定的 Engine 对象上的事务/连接资源。当连接被返回到连接池时,其事务状态也将被回滚。

默认情况下,当 Session 被关闭时,其状态与刚创建时本质上相同,并且 可以再次使用 。从这个意义上说,Session.close() 更像是一次“重置”(reset)操作,而不是传统意义上的“关闭数据库连接”操作。在这种使用模式下,方法 Session.reset() 实际上是 Session.close() 的别名,行为完全一致。

Session.close() 的默认行为可以通过将参数 Session.close_resets_only 设置为 False 来更改,表示在调用 Session.close() 之后,该 Session 不能再被重复使用。在此模式下,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))

# 会话在此自动关闭

在 1.4 版本发生变更: Session 对象现在采用延迟“开始事务”机制,详见 自动开始。调用 Session.close() 后不再立即开启新的事务。

The Session.close() method issues a Session.expunge_all() which removes all ORM-mapped objects from the session, and releases any transactional/connection resources from the Engine object(s) to which it is bound. When connections are returned to the connection pool, transactional state is rolled back as well.

By default, when the Session is closed, it is essentially in the original state as when it was first constructed, and may be used again. In this sense, the Session.close() method is more like a “reset” back to the clean state and not as much like a “database close” method. In this mode of operation the method Session.reset() is an alias to Session.close() and behaves in the same way.

The default behavior of Session.close() can be changed by setting the parameter Session.close_resets_only to False, indicating that the Session cannot be reused after the method Session.close() has been called. In this mode of operation the Session.reset() method will allow multiple “reset” of the session, behaving like Session.close() when Session.close_resets_only is set to True.

在 2.0.22 版本加入.

It’s recommended that the scope of a Session be limited by a call to Session.close() at the end, especially if the Session.commit() or Session.rollback() methods are not used. The Session may be used as a context manager to ensure that Session.close() is called:

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

# closes session automatically

在 1.4 版本发生变更: The Session object features deferred “begin” behavior, as described in autobegin. no longer immediately begins a new transaction after the Session.close() method is called.

会话常见问题

Session Frequently Asked Questions

此时,大多数用户通常已经对会话对象有了一些疑问。以下是一个简要的“迷你 FAQ”部分(请注意,我们也有一个更完整的 常见问题解答),列出了使用 Session 时最常见的问题。

By this point, many users already have questions about sessions. This section presents a mini-FAQ (note that we have also a real FAQ) of the most basic issues one is presented with when using a Session.

我什么时候创建 sessionmaker

When do I make a sessionmaker?

Q:我应该在哪里创建 sessionmaker?

A:只需在你的应用程序的全局作用域中创建一次即可。你可以把它看作应用程序配置的一部分。例如,如果你的应用有三个 .py 文件组成的包,你可以把 sessionmaker 放在 __init__.py 文件中。这样其他模块只需执行 from mypackage import Session。其他人就只需使用 Session(),其配置由那个中心点控制。

Q:如果我在应用启动时还不知道将连接到哪个数据库怎么办?

A:你可以稍后在“类级别”通过 sessionmaker.configure() 将引擎绑定到 Session

在本节的示例中,我们经常会在调用 Session 的代码上方直接创建 sessionmaker ,但这仅是为了简化示例!在实际应用中,sessionmaker 通常会定义在模块级别,而实际实例化 Session 的调用则应放在开始数据库操作的地方。

Just one time, somewhere in your application’s global scope. It should be looked upon as part of your application’s configuration. If your application has three .py files in a package, you could, for example, place the sessionmaker line in your __init__.py file; from that point on your other modules say “from mypackage import Session”. That way, everyone else just uses Session(), and the configuration of that session is controlled by that central point.

If your application starts up, does imports, but does not know what database it’s going to be connecting to, you can bind the Session at the “class” level to the engine later on, using sessionmaker.configure().

In the examples in this section, we will frequently show the sessionmaker being created right above the line where we actually invoke Session. But that’s just for example’s sake! In reality, the sessionmaker would be somewhere at the module level. The calls to instantiate Session would then be placed at the point in the application where database conversations begin.

我什么时候构造 Session,什么时候提交它,什么时候关闭它?

When do I construct a Session, when do I commit it, and when do I close it?

Session 通常在一个逻辑操作开始时构建,此时可能会涉及数据库访问。

每当 Session 与数据库进行交互时,它会立即开启一个数据库事务。该事务会持续进行,直到显式调用 Session 的 rollback、commit 或 close 方法。当上一个事务结束后,如果再次使用该会话,则会开启一个新的事务。因此,Session 支持跨多个事务存在(但同一时间仅有一个事务处于活动状态)。我们将这两个概念称为 事务范围(transaction scope)会话范围(session scope)

尽管应用架构可能千差万别,但确定 Session 开始与结束的范围通常并不困难。

以下是一些常见示例场景:

  • Web 应用:此类应用通常使用 Web 框架所提供的 SQLAlchemy 集成功能。基本模式是:在请求开始时创建 Session,在处理 POST、PUT 或 DELETE 请求后调用 Session.commit(),最后在请求结束时关闭会话。建议将 Session.expire_on_commit 设置为 False,以便在事务提交后访问对象时不再触发新的 SQL 查询。

  • 后台守护进程:如果后台服务会生成子进程(fork),应在每个子进程中本地创建一个 Session,在该子进程所处理的“任务”生命周期中使用该会话,并在任务完成后销毁它。

  • 命令行脚本:在此场景下,应用程序会在程序开始执行任务时创建一个全局 Session,并在任务完成时进行提交。

  • GUI 驱动应用:对于图形界面驱动的程序,会话的作用范围可能对应一个用户触发的事件(如点击按钮),也可能对应用户操作的一个周期(如用户“打开”一批记录并“保存”它们)。

总的原则是, 应用应在函数之外管理会话的生命周期。这是关注点分离(separation of concerns)的关键做法,确保数据操作逻辑不依赖于会话的上下文。

例如, 不要这样做:

### 这是错误的做法 ###

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 与其事务的作用范围:

### 这是更好的做法(但不是唯一的方式)###

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 现在可以作为上下文管理器使用,而无需外部辅助函数。

A Session is typically constructed at the beginning of a logical operation where database access is potentially anticipated.

The Session, whenever it is used to talk to the database, begins a database transaction as soon as it starts communicating. This transaction remains in progress until the Session is rolled back, committed, or closed. The Session will begin a new transaction if it is used again, subsequent to the previous transaction ending; from this it follows that the Session is capable of having a lifespan across many transactions, though only one at a time. We refer to these two concepts as transaction scope and session scope.

It’s usually not very hard to determine the best points at which to begin and end the scope of a Session, though the wide variety of application architectures possible can introduce challenging situations.

Some sample scenarios include:

  • Web applications. In this case, it’s best to make use of the SQLAlchemy integrations provided by the web framework in use. Or otherwise, the basic pattern is create a Session at the start of a web request, call the Session.commit() method at the end of web requests that do POST, PUT, or DELETE, and then close the session at the end of web request. It’s also usually a good idea to set Session.expire_on_commit to False so that subsequent access to objects that came from a Session within the view layer do not need to emit new SQL queries to refresh the objects, if the transaction has been committed already.

  • A background daemon which spawns off child forks would want to create a Session local to each child process, work with that Session through the life of the “job” that the fork is handling, then tear it down when the job is completed.

  • For a command-line script, the application would create a single, global Session that is established when the program begins to do its work, and commits it right as the program is completing its task.

  • For a GUI interface-driven application, the scope of the Session may best be within the scope of a user-generated event, such as a button push. Or, the scope may correspond to explicit user interaction, such as the user “opening” a series of records, then “saving” them.

As a general rule, the application should manage the lifecycle of the session externally to functions that deal with specific data. This is a fundamental separation of concerns which keeps data-specific operations agnostic of the context in which they access and manipulate that data.

E.g. don’t do this:

### 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()

Keep the lifecycle of the session (and usually the transaction) separate and external. The example below illustrates how this might look, and additionally makes use of a Python context manager (i.e. the with: keyword) in order to manage the scope of the Session and its transaction automatically:

### 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 版本发生变更: The Session may be used as a context manager without the use of external helper functions.

会话是缓存吗?

Is the Session a cache?

呃……不完全是。虽然会话(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 才不会向数据库发起查询请求。

此外,会话默认使用 弱引用(weak reference) 来存储对象实例。这也进一步限制了将会话作为缓存使用的可行性。

Session不是设计为全局对象 ,不能作为全局“注册表”被所有地方查阅使用。真正适合做这件事的是所谓的 二级缓存(second level cache)

SQLAlchemy 提供了一种基于 [dogpile.cache](https://dogpilecache.readthedocs.io/) 的二级缓存实现模式,可参考文档中的 Dogpile 缓存 示例。

Yeee…no. It’s somewhat used as a cache, in that it implements the identity map pattern, and stores objects keyed to their primary key. However, it doesn’t do any kind of query caching. This means, if you say session.scalars(select(Foo).filter_by(name='bar')), even if Foo(name='bar') is right there, in the identity map, the session has no idea about that. It has to issue SQL to the database, get the rows back, and then when it sees the primary key in the row, then it can look in the local identity map and see that the object is already there. It’s only when you say query.get({some primary key}) that the Session doesn’t have to issue a query.

Additionally, the Session stores object instances using a weak reference by default. This also defeats the purpose of using the Session as a cache.

The Session is not designed to be a global object from which everyone consults as a “registry” of objects. That’s more the job of a second level cache. SQLAlchemy provides a pattern for implementing second level caching using dogpile.cache, via the Dogpile 缓存 example.

如何获取某个对象的 Session

How can I get the Session for a certain object?

你可以使用 Session.object_session() 类方法来获取某个对象所属的会话实例:

session = Session.object_session(someobject)

也可以使用较新的 运行时审查 API 检查系统:

from sqlalchemy import inspect

session = inspect(someobject).session

Use the Session.object_session() classmethod available on Session:

session = Session.object_session(someobject)

The newer 运行时审查 API system can also be used:

from sqlalchemy import inspect

session = inspect(someobject).session

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

Is the Session thread-safe? Is AsyncSession safe to share in concurrent tasks?

Session 是一个 可变的、有状态的 对象,表示 单个数据库事务。因此,Session 的实例 不能在并发的线程或 asyncio 任务之间共享,除非进行仔细的同步Session 设计为 非并发 使用,也就是说,一个特定的 Session 实例应该仅在一个线程或任务中使用一次。

当使用 SQLAlchemy 的 asyncio 扩展中的 AsyncSession 对象时,该对象仅是 Session 上的一个薄代理,并且适用相同的规则;它是一个 无同步的、可变的、有状态的对象,因此 不能 在多个 asyncio 任务中同时使用同一个 AsyncSession 实例。

SessionAsyncSession 的实例表示单个逻辑数据库事务,只引用绑定到该对象的特定 :class:`_engine.Connection`(注意:这些对象都支持同时绑定多个引擎,但在事务范围内,每个引擎只有一个连接在起作用)。

在事务中的数据库连接也是一个有状态的对象,设计上用于按顺序、非并发地操作。命令会按顺序在连接上发出,由数据库服务器按发出的顺序处理这些命令。随着 Session 在该连接上发出命令并接收结果,Session 本身经历了与该连接上的命令和数据状态一致的内部状态变化;这些状态包括事务是否已开始、已提交或已回滚,是否有 SAVEPOINT(保存点)等,以及数据库行与本地 ORM 映射对象之间的状态同步。

在设计数据库并发应用程序时,适当的模型是每个并发任务或线程都使用自己的数据库事务。这就是为什么在讨论数据库并发时,标准术语通常是 多个并发事务。在传统的关系型数据库管理系统(RDBMS)中,没有一个类比可以对应一个正在接收和处理多个命令的单个数据库事务。

因此,SQLAlchemy 的 SessionAsyncSession 的并发模型是 每个线程一个 Session,每个任务一个 AsyncSession。如果应用程序使用多个线程,或者在 asyncio 中使用多个任务(例如通过 asyncio.gather()),就需要确保每个线程有自己的 Session,每个 asyncio 任务有自己的 AsyncSession

为了确保这种使用,最好的方式是在线程或任务的顶级 Python 函数内使用 标准上下文管理器模式,这将确保 SessionAsyncSession 的生命周期保持在本地作用域内。

对于需要一个“全局” Session 的应用程序,如果不能将 Session 对象传递给需要它的特定函数和方法,可以使用 scoped_session 方式提供一个“线程本地”的 Session 对象;有关背景,请参见 上下文/线程本地会话。在 asyncio 上下文中,async_scoped_session 对象是 scoped_session 的 asyncio 类比,但它配置起来更具挑战性,因为它需要一个自定义的“上下文”函数。

The Session is a mutable, stateful object that represents a single database transaction. An instance of Session therefore cannot be shared among concurrent threads or asyncio tasks without careful synchronization. The Session is intended to be used in a non-concurrent fashion, that is, a particular instance of Session should be used in only one thread or task at a time.

When using the AsyncSession object from SQLAlchemy’s asyncio extension, this object is only a thin proxy on top of a Session, and the same rules apply; it is an unsynchronized, mutable, stateful object, so it is not safe to use a single instance of AsyncSession in multiple asyncio tasks at once.

An instance of Session or AsyncSession represents a single logical database transaction, referencing only a single Connection at a time for a particular Engine or AsyncEngine to which the object is bound (note that these objects both support being bound to multiple engines at once, however in this case there will still be only one connection per engine in play within the scope of a transaction).

A database connection within a transaction is also a stateful object that is intended to be operated upon in a non-concurrent, sequential fashion. Commands are issued on the connection in a sequence, which are handled by the database server in the exact order in which they are emitted. As the Session emits commands upon this connection and receives results, the Session itself is transitioning through internal state changes that align with the state of commands and data present on this connection; states which include if a transaction were begun, committed, or rolled back, what SAVEPOINTs if any are in play, as well as fine-grained synchronization of the state of individual database rows with local ORM-mapped objects.

When designing database applications for concurrency, the appropriate model is that each concurrent task / thread works with its own database transaction. This is why when discussing the issue of database concurrency, the standard terminology used is multiple, concurrent transactions. Within traditional RDMS there is no analogue for a single database transaction that is receiving and processing multiple commands concurrently.

The concurrency model for SQLAlchemy’s Session and AsyncSession is therefore Session per thread, AsyncSession per task. An application that uses multiple threads, or multiple tasks in asyncio such as when using an API like asyncio.gather() would want to ensure that each thread has its own Session, each asyncio task has its own AsyncSession.

The best way to ensure this use is by using the standard context manager pattern locally within the top level Python function that is inside the thread or task, which will ensure the lifespan of the Session or AsyncSession is maintained within a local scope.

For applications that benefit from having a “global” Session where it’s not an option to pass the Session object to specific functions and methods which require it, the scoped_session approach can provide for a “thread local” Session object; see the section 上下文/线程本地会话 for background. Within the asyncio context, the async_scoped_session object is the asyncio analogue for scoped_session, however is more challenging to configure as it requires a custom “context” function.