SqlAlchemy 2.0 中文文档(二十二)(2)

本文涉及的产品
云原生数据库 PolarDB PostgreSQL 版,企业版 4核16GB
推荐场景:
HTAP混合负载
云原生数据库 PolarDB MySQL 版,Serverless 5000PCU 100GB
云原生数据库 PolarDB MySQL 版,通用型 2核4GB 50GB
简介: SqlAlchemy 2.0 中文文档(二十二)

SqlAlchemy 2.0 中文文档(二十二)(1)https://developer.aliyun.com/article/1560447


我什么时候构建一个 Session,什么时候提交它,什么时候关闭它?

一个 Session 通常在可能预期到数据库访问的逻辑操作开始时构建。

每当 Session 用于与数据库通信时,它会在开始通信时立即启动数据库事务。此事务将持续进行,直到 Session 被回滚、提交或关闭。如果再次使用 Session,则会开始新的事务,接着之前的事务结束;由此可知,Session 可以跨多个事务具有生命周期,但一次只能进行一个。我们将这两个概念称为 事务范围会话范围

通常很容易确定何时开始和结束 Session 的范围,尽管可能存在多种应用程序架构,可能会引入具有挑战性的情况。

一些示例场景包括:

  • Web 应用程序。在这种情况下,最好利用所使用的 Web 框架提供的 SQLAlchemy 集成。或者,基本模式是在 Web 请求开始时创建一个Session,在执行 POST、PUT 或 DELETE 的 Web 请求结束时调用 Session.commit() 方法,然后在 Web 请求结束时关闭会话。通常还应该设置 Session.expire_on_commit 为 False,以便来自视图层的对象在事务已经提交后不需要发出新的 SQL 查询来刷新对象。
  • 一个后台守护进程会生成子进程,可能会想要为每个子进程创建一个Session,在该子进程处理的“作业”生命周期内使用该Session,然后在作业完成时将其销毁。
  • 对于命令行脚本,应用程序会创建一个在程序开始工作时建立的单一全局 Session,并在程序完成任务时立即提交它。
  • 对于基于 GUI 接口的应用程序,Session 的范围可能最好在用户生成的事件范围内,比如按钮点击。或者,范围可能与明确的用户交互相对应,比如用户“打开”一系列记录,然后“保存”它们。

一般来说,应用程序应该在处理特定数据的函数外部管理会话的生命周期。这是一种基本的关注点分离,使得特定于数据的操作不受其访问和操作数据的上下文的影响。

例如,不要这样做

### this is the **wrong way to do it** ###
class ThingOne:
    def go(self):
        session = Session()
        try:
            session.execute(update(FooBar).values(x=5))
            session.commit()
        except:
            session.rollback()
            raise
class ThingTwo:
    def go(self):
        session = Session()
        try:
            session.execute(update(Widget).values(q=18))
            session.commit()
        except:
            session.rollback()
            raise
def run_my_program():
    ThingOne().go()
    ThingTwo().go()

将会话(通常也是事务)的生命周期分离和外部化。下面的示例说明了这样做的方式,并且另外使用了 Python 上下文管理器(即 with: 关键字)来自动管理 Session 及其事务的范围:

### this is a **better** (but not the only) way to do it ###
class ThingOne:
    def go(self, session):
        session.execute(update(FooBar).values(x=5))
class ThingTwo:
    def go(self, session):
        session.execute(update(Widget).values(q=18))
def run_my_program():
    with Session() as session:
        with session.begin():
            ThingOne().go(session)
            ThingTwo().go(session)

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

会话是缓存吗?

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

另外,默认情况下,会话(Session)使用弱引用来存储对象实例。这也违背了使用会话作为缓存的初衷。

Session不是设计为所有人都可以向其查询作为“注册表”的全局对象。这更像是第二级缓存的工作。SQLAlchemy 提供了使用dogpile.cache实现第二级缓存的模式,通过 Dogpile Caching 示例。

如何获取某个对象的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 映射的对象同步的细粒度同步。

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

因此,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 模拟,但是更难配置,因为它需要一个自定义的“上下文”函数。

Session 做什么?

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

Session 在最常见的使用模式下,以大部分无状态的形式开始。一旦发出查询或使用其他对象进行持久化,它会从与 Session 关联的 Engine 请求连接资源,然后在该连接上建立事务。这个事务会一直保持到 Session 被指示提交或回滚事务。事务结束时,与 Engine 关联的连接资源会被释放到引擎管理的连接池中。然后,一个新的事务会开始,使用一个新的连接。

Session 维护的 ORM 对象被装饰,这样每当 Python 程序中的属性或集合被修改时,就会生成一个变更事件,由 Session 记录下来。每当即将查询数据库或即将提交事务时,Session 首先会将内存中的所有待定更改刷新到数据库中。这被称为工作单元模式。

当使用 Session 时,将其维护的 ORM 映射对象视为代理对象以访问数据库行,这些对象局限于由 Session 持有的事务。为了保持对象的状态与实际数据库中的内容一致,有多种事件会导致对象重新访问数据库以保持同步。可以将对象“分离”(detach)出 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参数来禁用此行为。更多细节请参阅 Committing 部分。

我们还可以将 Session.commit() 调用和事务的整体“框架”封装在一个上下文管理器中,对于那些需要将数据提交到数据库的情况。所谓的“框架”是指如果所有操作都成功,则会调用 Session.commit() 方法,但如果引发任何异常,则会立即调用 Session.rollback() 方法,以便立即回滚事务,然后向外传播异常。在 Python 中,这主要是使用 try: / except: / else: 块来表达的,例如:

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

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

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

更简洁地说,这两个上下文可以合并:

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

使用 sessionmaker

sessionmaker 的目的是为具有固定配置的 Session 对象提供一个工厂。由于典型的情况是应用程序将在模块范围内拥有一个 Engine 对象,sessionmaker 可以为针对此引擎的 Session 对象提供一个工厂:

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
# an Engine, which the Session will use for connection
# resources, typically in module scope
engine = create_engine("postgresql+psycopg2://scott:tiger@localhost/")
# a sessionmaker(), also in the same scope as the engine
Session = sessionmaker(engine)
# we can now construct a Session() without needing to pass the
# engine each time
with Session() as session:
    session.add(some_object)
    session.add(some_other_object)
    session.commit()
# closes the session

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

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
# an Engine, which the Session will use for connection
# resources
engine = create_engine("postgresql+psycopg2://scott:tiger@localhost/")
# a sessionmaker(), also in the same scope as the engine
Session = sessionmaker(engine)
# we can now construct a Session() and include begin()/commit()/rollback()
# at once
with Session.begin() as session:
    session.add(some_object)
    session.add(some_other_object)
# commits the transaction, closes the session

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

当您编写应用程序时,sessionmaker工厂应该与由create_engine()创建的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”风格查询现在是标准的。请参阅 2.0 迁移 - ORM 使用了解从 1.x 系列迁移的注意事项。

另请参阅

ORM 查询指南 ### 添加新项目或现有项目

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

user1 = User(name="user1")
user2 = User(name="user2")
session.add(user1)
session.add(user2)
session.commit()  # write changes to the database

要一次向会话中添加一系列项目,请使用Session.add_all()

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

Session.add() 操作沿着 save-update 级联进行。有关详细信息,请参阅 Cascades 部分。### 删除

Session.delete() 方法将一个实例放入会话的待删除对象列表中:

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

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

有关Session.delete() 操作的各种重要行为,特别是关于如何处理与其他对象和集合的关系的行为。有关此工作原理的更多信息,请参阅 Cascades 部分,但总的来说规则是:

  • 通过relationship()指令关联到已删除对象的映射对象行默认不会被删除。如果这些对象具有指回被删除行的外键约束,这些列将设置为 NULL。如果列是非空的,这将导致约束违规。
  • 要将相关对象行的“SET NULL”更改为删除,请在 relationship() 上使用 delete 级联。
  • 当链接为“many-to-many”表的表中的行通过relationship.secondary参数链接时,当它们所引用的对象被删除时,它们在所有情况下都将被删除。
  • 当相关对象包含指回正在删除的对象的外键约束,并且它们所属的相关集合当前未加载到内存中时,工作单元将发出一个  SELECT 来获取所有相关行,以便它们的主键值可以用于发出 UPDATE 或 DELETE 语句以处理这些相关行。通过这种方式,ORM  即使在 Core ForeignKeyConstraint 对象上配置了 ON DELETE CASCADE,也会执行这个功能,而不需要进一步的指示。
  • relationship.passive_deletes参数可用于调整此行为,并更自然地依赖于“ON DELETE CASCADE”;当设置为 True 时,此 SELECT 操作将不再发生,但仍然会对本地存在的行进行显式的 SET NULL 或 DELETE。将relationship.passive_deletes设置为字符串"all"将禁用所有相关对象的更新/删除。
  • 当标记为删除的对象发生删除时,该对象不会自动从引用它的集合或对象引用中移除。当Session过期时,这些集合可能会再次加载,以便对象不再存在。然而,最好的做法是,不要对这些对象使用Session.delete(),而是应该从其集合中移除对象,然后使用  delete-orphan 以便作为该集合移除的次要效果而被删除。请参阅 Notes on Delete - Deleting Objects  Referenced from Collections and Scalar Relationships 部分以获取示例。

另请参见

delete - 描述了“删除级联”,当主对象被删除时,会标记相关对象以进行删除。

delete-orphan - 描述了“删除孤儿级联”,当相关对象与其主对象解除关联时,会标记这些相关对象以进行删除。

Notes on Delete - Deleting Objects Referenced from Collections and Scalar Relationships - 关于Session.delete()的重要背景,涉及在内存中刷新关系。### 刷新

Session 使用其默认配置时,刷新步骤几乎总是透明进行的。具体来说,在由 Query 或 2.0 风格 的 Session.execute() 调用导致发出任何单个 SQL 语句之前,以及在 Session.commit() 调用中在提交事务之前,都会发生刷新。当使用 Session.begin_nested() 时,还会在发出 SAVEPOINT 之前发生刷新。

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

session.flush()

在某些方法的范围内自动发生的刷新称为自动刷新。自动刷新定义为在包括以下方法的开头发生的可配置的自动刷新调用:

  • Session.execute() 和其他执行 SQL 的方法,在针对启用了 ORM 的 SQL 构造时使用,比如指向 ORM 实体和/或 ORM 映射属性的 select() 对象
  • 当调用 Query 发送 SQL 到数据库时
  • 在查询数据库之前 Session.merge() 方法中
  • 当对象被刷新
  • 当针对未加载的对象属性执行 ORM 延迟加载 操作时。

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

  • Session.commit() 方法的过程中
  • 调用 Session.begin_nested()
  • 当使用 Session.prepare() 2PC 方法时。

对于上述项目列表所应用的自动刷新行为,可以通过构建一个传递了Session.autoflush参数为FalseSessionsessionmaker来禁用它:

Session = sessionmaker(autoflush=False)

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

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

重申一下: 当调用事务方法(如Session.commit()Session.begin_nested())时,刷新过程总是发生,无论任何“自动刷新”设置如何,当Session仍有待处理的更改时。

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

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

另请参阅

“由于刷新期间发生的先前异常,此会话的事务已回滚。”(或类似) - 关于在刷新失败时必须调用Session.rollback()的更多背景信息。 ### 按主键获取

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

my_user = session.get(User, 5)

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

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

  • expire() 方法 - Session.expire() 方法将擦除对象的选定或全部属性的内容,以便在下次访问它们时从数据库加载,例如使用惰性加载模式:
session.expire(u1)
u1.some_attribute  # <-- lazy loads from the transaction
  • refresh() 方法 - 与之密切相关的是Session.refresh() 方法,它执行Session.expire() 方法执行的所有操作,但还立即发出一个或多个 SQL 查询来实际刷新对象的内容:
session.refresh(u1)  # <-- emits a SQL query
u1.some_attribute  # <-- is refreshed from the transaction
  • populate_existing() 方法或执行选项 - 现在这是一个在填充现有中记录的执行选项;在传统形式中,它在Query对象上作为Query.populate_existing()方法找到。无论采取哪种形式,此操作表示应从数据库中的内容无条件地重新填充从查询返回的对象:
u2 = session.scalars(
    select(User).where(User.id == 5).execution_options(populate_existing=True)
).one()

关于刷新/过期概念的进一步讨论可在刷新/过期找到。

另请参阅

刷新/过期

我正在使用我的会话重新加载数据,但它没有看到我在其他地方提交的更改

使用任意 WHERE 子句的 UPDATE 和 DELETE

SQLAlchemy 2.0 包括增强功能,可发出几种类型的 ORM 启用的 INSERT、UPDATE 和 DELETE 语句。有关文档,请参阅 ORM-启用的 INSERT、UPDATE 和 DELETE 语句。

另请参阅

ORM-启用的 INSERT、UPDATE 和 DELETE 语句

带有自定义 WHERE 条件的 ORM UPDATE 和 DELETE

自动开始

Session 对象具有称为autobegin的行为。这表示Session一旦执行了与Session相关的任何工作,无论涉及对Session的内部状态进行修改还是需要数据库连接的操作,它都会在内部将自身视为处于“事务”状态。

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

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

也可以通过调用Session.begin()方法显式启动Session的事务状态。调用此方法时,Session无条件地被置于“事务性”状态。Session.begin()可以像描述的那样用作上下文管理器,详见构建开始 / 提交 / 回滚块的框架。

禁用 Autobegin 以防止隐式事务

可以通过将Session.autobegin参数设置为False来禁用“自动开始”行为。通过使用此参数,Session将要求显式调用Session.begin()方法。在构造之后以及在调用任何Session.rollback()Session.commit()Session.close()方法之后,如果尝试在没有首先调用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,允许禁用“自动开始”行为 ### 提交

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

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

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

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

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

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

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

另请参阅

自动开始 ### 回滚

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

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

  • 数据库事务被回滚。对于绑定到单个EngineSession,这意味着对当前正在使用的最多一个Connection进行回滚。对于绑定到多个Engine对象的Session对象,将对所有被检出的Connection对象进行回滚。

  • 数据库连接被释放。这遵循了提交中注意到的与连接相关的相同行为,即从Engine对象获取的Connection对象被关闭,导致 DBAPI 连接被释放到 Engine 中的连接池中。如果有新的事务开始,会从Engine中检出新的连接。

  • 对于直接绑定到ConnectionSession,如将会话加入外部事务(例如测试套件)中描述的,此Connection上的回滚行为将遵循由Session.join_transaction_mode参数指定的行为,这可能涉及回滚保存点或发出真正的 ROLLBACK。

  • 在事务的生命周期内,当对象被添加到Session时最初处于挂起状态的对象将被清除,对应其 INSERT 语句被回滚的情况。它们属性的状态保持不变。

  • 在事务的生命周期内被标记为已删除的对象将被提升回持久状态,对应其 DELETE 语句被回滚的情况。请注意,如果这些对象在事务中首先处于挂起状态,则该操作优先级较高。

  • 所有未清除的对象都将完全过期 - 这与Session.expire_on_commit设置无关。

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

从版本 1.4 开始更改:Session对象现在具有延迟“begin”行为,如自动开始中所述。如果未开始任何事务,则Session.commit()Session.rollback()等方法将不起作用。在 1.4 之前,不会观察到这种行为,因为在非自动提交模式下,事务总是隐式存在的。

Session.flush()失败时,通常是由于主键、外键或“非空”约束违规等原因,会自动发出 ROLLBACK(当前不可能在部分失败后继续 flush)。但是,在此时,Session进入一种称为“不活跃”的状态,调用应用程序必须始终显式调用Session.rollback()方法,以便Session可以回到可用状态(也可以简单地关闭并丢弃)。有关进一步讨论,请参阅 FAQ 条目“此会话的事务由于刷新时的先前异常而被回滚。”(或类似)。

另请参阅

自动开始 ### 结束

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

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

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

2.0.22 版本中的新增功能。

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

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

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

Session 可以自行构建,也可以使用 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参数来禁用此行为。更多详细信息请参见提交部分。

制定开始/提交/回滚块的框架

我们还可以将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

sessionmakerEngine 类似,是用于函数级会话/连接的模块级工厂。因此,它还有自己的 sessionmaker.begin() 方法,类似于 Engine.begin(),它返回一个 Session 对象,并且还维护一个开始/提交/回滚块:

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 工厂应该与 create_engine() 创建的 Engine 对象保持相同的作用域,通常在模块级或全局级别。由于这些对象都是工厂,因此它们可以被任意数量的函数和线程同时使用。

另请参阅

sessionmaker

Session


SqlAlchemy 2.0 中文文档(二十二)(3)https://developer.aliyun.com/article/1560451

相关实践学习
使用PolarDB和ECS搭建门户网站
本场景主要介绍基于PolarDB和ECS实现搭建门户网站。
阿里云数据库产品家族及特性
阿里云智能数据库产品团队一直致力于不断健全产品体系,提升产品性能,打磨产品功能,从而帮助客户实现更加极致的弹性能力、具备更强的扩展能力、并利用云设施进一步降低企业成本。以云原生+分布式为核心技术抓手,打造以自研的在线事务型(OLTP)数据库Polar DB和在线分析型(OLAP)数据库Analytic DB为代表的新一代企业级云原生数据库产品体系, 结合NoSQL数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
相关文章
|
12天前
|
SQL 数据库 Python
SqlAlchemy 2.0 中文文档(二十六)(4)
SqlAlchemy 2.0 中文文档(二十六)
17 2
|
12天前
|
SQL 缓存 数据库连接
SqlAlchemy 2.0 中文文档(二十二)(3)
SqlAlchemy 2.0 中文文档(二十二)
13 5
|
12天前
|
SQL 存储 数据库连接
SqlAlchemy 2.0 中文文档(二十二)(1)
SqlAlchemy 2.0 中文文档(二十二)
16 2
|
12天前
|
SQL 缓存 数据库连接
SqlAlchemy 2.0 中文文档(二十二)(5)
SqlAlchemy 2.0 中文文档(二十二)
11 1
|
12天前
|
SQL 缓存 数据库连接
SqlAlchemy 2.0 中文文档(二十六)(3)
SqlAlchemy 2.0 中文文档(二十六)
12 2
|
12天前
|
SQL 缓存 数据库连接
SqlAlchemy 2.0 中文文档(二十六)(1)
SqlAlchemy 2.0 中文文档(二十六)
16 2
|
12天前
|
自然语言处理 数据库 Python
SqlAlchemy 2.0 中文文档(二十六)(2)
SqlAlchemy 2.0 中文文档(二十六)
13 2
|
12天前
|
缓存 自然语言处理 数据库
SqlAlchemy 2.0 中文文档(二十六)(5)
SqlAlchemy 2.0 中文文档(二十六)
16 1
|
12天前
|
SQL 前端开发 API
SqlAlchemy 2.0 中文文档(二十七)(1)
SqlAlchemy 2.0 中文文档(二十七)
14 1
|
12天前
|
SQL 缓存 安全
SqlAlchemy 2.0 中文文档(二十二)(4)
SqlAlchemy 2.0 中文文档(二十二)
9 0