原文:
docs.sqlalchemy.org/en/20/contents.html
级联
原文:
docs.sqlalchemy.org/en/20/orm/cascades.html
映射器支持在relationship()
构造上配置可配置级联行为的概念。这涉及到相对于特定Session
上执行的操作应如何传播到由该关系引用的项目(例如“子”对象),并且受到relationship.cascade
选项的影响。
级联的默认行为仅限于所谓的 save-update 和 merge 设置的级联。级联的典型“替代”设置是添加 delete 和 delete-orphan 选项;这些设置适用于只有在附加到其父对象时才存在的相关对象,并且在其他情况下将被删除。
使用relationship()
上的relationship.cascade
选项配置级联行为:
class Order(Base):
__tablename__ = "order"
items = relationship("Item", cascade="all, delete-orphan")
customer = relationship("User", cascade="save-update")
要在反向引用上设置级联,可以使用相同的标志与backref()
函数一起使用,该函数最终将其参数反馈到relationship()
中:
class Item(Base):
__tablename__ = "item"
order = relationship(
"Order", backref=backref("items", cascade="all, delete-orphan")
)
relationship.cascade
的默认值为save-update, merge
。此参数的典型替代设置为all
或更常见的是all, delete-orphan
。all
符号是save-update, merge, refresh-expire, expunge, delete
的同义词,与delete-orphan
结合使用表示子对象应在所有情况下跟随其父对象,并且一旦不再与该父对象关联就应该被删除。
警告
all
级联选项意味着 refresh-expire 级联设置,当使用异步 I/O(asyncio)扩展时可能不可取,因为它将比在显式 I/O 上下文中通常适当地更积极地使相关对象过期。有关更多背景信息,请参阅在使用 AsyncSession 时防止隐式 I/O 中的注释。
可以为relationship.cascade
参数指定的可用值列表在以下各小节中进行描述。
save-update
save-update
级联指示当通过Session.add()
将对象放入Session
中时,通过这个relationship()
与之关联的所有对象也应该添加到同一个Session
中。假设我们有一个对象user1
,其中包含两个相关对象address1
、address2
:
>>> user1 = User()
>>> address1, address2 = Address(), Address()
>>> user1.addresses = [address1, address2]
如果我们将user1
添加到Session
中,它也会隐式添加address1
、address2
:
>>> sess = Session()
>>> sess.add(user1)
>>> address1 in sess
True
save-update
级联也会影响已经存在于Session
中的对象的属性操作。如果我们将第三个对象address3
添加到user1.addresses
集合中,它将成为该Session
的状态的一部分:
>>> address3 = Address()
>>> user1.addresses.append(address3)
>>> address3 in sess
True
当从集合中移除一个项目或将对象从标量属性中解除关联时,save-update
级联可能会表现出令人惊讶的行为。在某些情况下,被孤立的对象仍然可能被拉入原父级的Session
中;这是为了使刷新过程可以适当地处理相关对象。这种情况通常只会在一个对象从一个Session
中移除并添加到另一个对象时出现:
>>> user1 = sess1.scalars(select(User).filter_by(id=1)).first()
>>> address1 = user1.addresses[0]
>>> sess1.close() # user1, address1 no longer associated with sess1
>>> user1.addresses.remove(address1) # address1 no longer associated with user1
>>> sess2 = Session()
>>> sess2.add(user1) # ... but it still gets added to the new session,
>>> address1 in sess2 # because it's still "pending" for flush
True
save-update
级联默认启用,并且通常被视为理所当然;它通过允许单个调用Session.add()
一次性在该Session
中注册整个对象结构来简化代码。虽然它可以被禁用,但通常没有必要这样做。
双向关系中 save-update 级联的行为
在双向关系的上下文中,即使用relationship.back_populates
或relationship.backref
参数创建相互引用的两个独立的relationship()
对象时,save-update
级联是单向的。
当一个未关联Session
的对象被赋给与关联Session
相关的父对象的属性或集合时,它将自动添加到同一Session
中。然而,反向操作不会产生此效果;当一个未关联Session
的对象被赋给与关联Session
相关的子对象时,不会自动将该父对象添加到Session
中。这种行为的总体主题称为“级联反向引用”,并代表了从 SQLAlchemy 2.0 开始标准化的行为变更。
以示例说明,假设给定了一个Order
对象的映射,它与一系列Item
对象通过关系Order.items
和Item.order
双向关联:
mapper_registry.map_imperatively(
Order,
order_table,
properties={"items": relationship(Item, back_populates="order")},
)
mapper_registry.map_imperatively(
Item,
item_table,
properties={"order": relationship(Order, back_populates="items")},
)
如果一个Order
已经与一个Session
相关联,并且然后创建一个Item
对象并将其附加到该Order
的Order.items
集合中,Item
将自动级联到相同的Session
中:
>>> o1 = Order()
>>> session.add(o1)
>>> o1 in session
True
>>> i1 = Item()
>>> o1.items.append(i1)
>>> o1 is i1.order
True
>>> i1 in session
True
在上述情况下,Order.items
和Item.order
的双向性意味着附加到Order.items
也会赋值给Item.order
。同时,save-update
级联允许将Item
对象添加到与父Order
已关联的相同Session
中。
然而,如果上述操作以反向方向执行,即赋值Item.order
而不是直接附加到Order.item
,则级联操作不会自动进行,即使对象赋值Order.items
和Item.order
与上一个示例中的状态相同:
>>> o1 = Order()
>>> session.add(o1)
>>> o1 in session
True
>>> i1 = Item()
>>> i1.order = o1
>>> i1 in order.items
True
>>> i1 in session
False
在上述情况下,Item
对象创建并设置完所有期望的状态后,应明确将其添加到Session
中:
>>> session.add(i1)
在较旧版本的 SQLAlchemy 中,保存-更新级联在所有情况下都会双向发生。然后,使用一个称为cascade_backrefs
的选项使其成为可选项。最后,在 SQLAlchemy 1.4 中,旧行为被弃用,并且在 SQLAlchemy 2.0 中删除了cascade_backrefs
选项。其理由是用户通常不会觉得将对象的属性分配给对象上的属性是直观的,如上面所示的i1.order = o1
的分配,会改变对象i1
的持久状态,使其现在在Session
中处于挂起状态,并且在那些给定对象仍在构建并且尚未准备好被刷新的情况下,自动刷新会过早地刷新对象并导致错误。选择在单向和双向行为之间选择的选项也被删除,因为此选项创建了两种略有不同的工作方式,增加了 ORM 的整体学习曲线以及文档和用户支持负担。
另请参阅
在 2.0 中弃用以删除的 cascade_backrefs 行为 - 关于“级联反向引用”行为变更的背景 ## 删除
delete
级联表示当“父”对象标记为删除时,其相关的“子”对象也应标记为删除。例如,如果我们有一个关系User.addresses
配置了delete
级联:
class User(Base):
# ...
addresses = relationship("Address", cascade="all, delete")
如果使用上述映射,我们有一个User
对象和两个相关的Address
对象:
>>> user1 = sess1.scalars(select(User).filter_by(id=1)).first()
>>> address1, address2 = user1.addresses
如果我们标记user1
进行删除,在刷新操作进行后,address1
和address2
也将被删除:
>>> sess.delete(user1)
>>> sess.commit()
DELETE FROM address WHERE address.id = ?
((1,), (2,))
DELETE FROM user WHERE user.id = ?
(1,)
COMMIT
或者,如果我们的User.addresses
关系没有delete
级联,SQLAlchemy 的默认行为是通过将它们的外键引用设置为NULL
来解除user1
与address1
和address2
的关联。使用以下映射:
class User(Base):
# ...
addresses = relationship("Address")
在删除父User
对象时,address
中的行不会被删除,而是被解除关联:
>>> sess.delete(user1)
>>> sess.commit()
UPDATE address SET user_id=? WHERE address.id = ?
(None, 1)
UPDATE address SET user_id=? WHERE address.id = ?
(None, 2)
DELETE FROM user WHERE user.id = ?
(1,)
COMMIT
在一对多关系中,delete
级联通常与delete-orphan
级联结合使用,如果“子”对象与父对象解除关联,则会为相关行发出 DELETE。delete
和delete-orphan
级联的组合涵盖了 SQLAlchemy 必须在将外键列设置为 NULL 与完全删除行之间做出决定的两种情况。
该功能默认完全独立于数据库配置的FOREIGN KEY
约束,这些约束本身可能配置CASCADE
行为。为了更有效地与此配置集成,应使用描述在使用 ORM 关系中的外键 ON DELETE 级联的附加指令。
警告
请注意,ORM 的“delete”和“delete-orphan”行为仅适用于使用Session.delete()
方法在 unit of work 过程中标记单个 ORM 实例以进行删除。它不适用于“批量”删除,这将使用delete()
构造发出,如在 ORM UPDATE and DELETE with Custom WHERE Criteria 中所示。有关更多背景信息,请参阅 ORM 启用的更新和删除的重要说明和警告。
另请参阅
使用 ORM 关系的外键 ON DELETE 级联
使用删除级联处理多对多关系
delete-orphan
使用删除级联处理多对多关系
cascade="all, delete"
选项在多对多关系中同样有效,该关系使用relationship.secondary
指示一个关联表。当删除父对象,因此与其相关对象解除关联时,工作单元过程通常会从关联表中删除行,但保留相关对象。与cascade="all, delete"
结合使用时,将为子行本身执行额外的DELETE
语句。
以下示例将 Many To Many 的示例调整为说明关联的一侧设置为cascade="all, delete"
:
association_table = Table(
"association",
Base.metadata,
Column("left_id", Integer, ForeignKey("left.id")),
Column("right_id", Integer, ForeignKey("right.id")),
)
class Parent(Base):
__tablename__ = "left"
id = mapped_column(Integer, primary_key=True)
children = relationship(
"Child",
secondary=association_table,
back_populates="parents",
cascade="all, delete",
)
class Child(Base):
__tablename__ = "right"
id = mapped_column(Integer, primary_key=True)
parents = relationship(
"Parent",
secondary=association_table,
back_populates="children",
)
在上述情况下,当使用Session.delete()
标记Parent
对象进行删除时,刷新过程通常会从association
表中删除相关行,但根据级联规则,它还将删除所有相关的Child
行。
警告
如果上述cascade="all, delete"
设置在两个关系上都配置了,则级联操作将继续通过所有Parent
和Child
对象进行级联,加载遇到的每个children
和parents
集合并删除所有连接的内容。通常不希望“delete”级联双向配置。
另请参阅
从多对多表中删除行
使用外键 ON DELETE 处理多对多关系 ### 使用 ORM 关系的外键 ON DELETE 级联处理
SQLAlchemy 的“delete”级联行为与数据库 FOREIGN KEY
约束的 ON DELETE
特性重叠。SQLAlchemy 允许使用 ForeignKey
和 ForeignKeyConstraint
构造配置这些模式级 DDL 行为;如何在 Table
元数据与这些对象的使用一起配置,在 ON UPDATE and ON DELETE 中有描述。
为了将 ON DELETE
外键级联与 relationship()
结合使用,首先必须注意 relationship.cascade
设置仍然必须配置为匹配所需的delete
或“set null”行为(使用 delete
级联或将其省略),以便无论是 ORM 还是数据库级约束将处理实际修改数据库中的数据的任务,ORM 仍将能够适当跟踪可能受到影响的本地存在对象的状态。
在 relationship()
上有一个额外的选项,指示 ORM 应该尝试自行运行与相关行的 DELETE/UPDATE 操作的程度,而不是依赖于期望数据库端的 FOREIGN KEY 约束级联处理任务;这是 relationship.passive_deletes
参数,它接受 False
(默认值)、True
和 "all"
选项。
最典型的例子是当删除父行时要删除子行,并且在相关的 FOREIGN KEY
约束上配置了 ON DELETE CASCADE
:
class Parent(Base):
__tablename__ = "parent"
id = mapped_column(Integer, primary_key=True)
children = relationship(
"Child",
back_populates="parent",
cascade="all, delete",
passive_deletes=True,
)
class Child(Base):
__tablename__ = "child"
id = mapped_column(Integer, primary_key=True)
parent_id = mapped_column(Integer, ForeignKey("parent.id", ondelete="CASCADE"))
parent = relationship("Parent", back_populates="children")
当删除父行时,上述配置的行为如下:
- 应用程序调用
session.delete(my_parent)
,其中my_parent
是Parent
的一个实例。 - 当
Session
下次将更改刷新到数据库时,my_parent.children
集合中的所有 当前加载的 项目都将由 ORM 删除,这意味着为每条记录发出一个DELETE
语句。 - 如果
my_parent.children
集合是未加载的,则不会发出DELETE
语句。如果在此relationship()
上未设置relationship.passive_deletes
标志,则将为未加载的Child
对象发出SELECT
语句。 - 然后会为
my_parent
行本身发出DELETE
语句。 - 数据库级别的
ON DELETE CASCADE
设置确保将删除所有引用受影响的parent
行的child
中的行。 - 由
my_parent
引用的Parent
实例,以及所有与该对象相关联且已加载(即执行了步骤 2)的Child
实例,将从Session
中解除关联。
注意
要使用“ON DELETE CASCADE”,底层数据库引擎必须支持FOREIGN KEY
约束,并且它们必须被强制执行:
- 在使用 MySQL 时,必须选择适当的存储引擎。有关详细信息,请参见 CREATE TABLE arguments including Storage Engines。
- 在使用 SQLite 时,必须显式启用外键支持。有关详细信息,请参见 Foreign Key Support。### 使用外键 ON DELETE 处理多对多关系
正如在使用级联删除处理多对多关系中描述的那样,“删除”级联也适用于多对多关系。要利用ON DELETE CASCADE
外键与多对多结合使用,需要在关联表上配置FOREIGN KEY
指令。这些指令可以处理自动从关联表中删除,但不能自动删除相关对象本身。
在这种情况下,relationship.passive_deletes
指令可以在删除操作期间节省一些额外的SELECT
语句,但仍然有一些集合,ORM 将继续加载它们,以定位受影响的子对象并正确处理它们。
注意
对此的假设优化可以包括一次针对关联表的所有父关联行的单个DELETE
语句,然后使用RETURNING
来定位受影响的相关子行,但是这目前不是 ORM 工作单元实现的一部分。
在这个配置中,我们在关联表的两个外键约束上都配置了ON DELETE CASCADE
。我们在父->子关系的一侧配置了cascade="all, delete"
,然后我们可以在双向关系的另一侧上配置passive_deletes=True
,如下所示:
association_table = Table(
"association",
Base.metadata,
Column("left_id", Integer, ForeignKey("left.id", ondelete="CASCADE")),
Column("right_id", Integer, ForeignKey("right.id", ondelete="CASCADE")),
)
class Parent(Base):
__tablename__ = "left"
id = mapped_column(Integer, primary_key=True)
children = relationship(
"Child",
secondary=association_table,
back_populates="parents",
cascade="all, delete",
)
class Child(Base):
__tablename__ = "right"
id = mapped_column(Integer, primary_key=True)
parents = relationship(
"Parent",
secondary=association_table,
back_populates="children",
passive_deletes=True,
)
使用上述配置,删除Parent
对象的操作如下:
- 使用
Session.delete()
标记要删除的Parent
对象。 - 当发生刷新时,如果未加载
Parent.children
集合,则 ORM 将首先发出 SELECT 语句,以加载与Parent.children
对应的Child
对象。 - 然后,将发出针对与该父行对应的
association
中的行的DELETE
语句。 - 对于每个受此立即删除影响的
Child
对象,由于配置了passive_deletes=True
,工作单元将不需要尝试为每个Child.parents
集合发出 SELECT 语句,因为假定将删除association
中的相应行。 - 对于从
Parent.children
加载的每个Child
对象,都会发出DELETE
语句。 ## delete-orphan
delete-orphan
级联会为delete
级联添加行为,这样当子对象与父对象取消关联时,子对象将被标记为删除,而不仅仅是在父对象被标记为删除时。当处理一个与父对象“拥有”关系的相关对象时,这是一种常见的特性,该关系具有 NOT NULL 外键,因此从父集合中删除项目会导致其被删除。
delete-orphan
级联意味着每个子对象一次只能有一个父对象,并且在绝大多数情况下,它只配置在一对多关系上。对于设置在多对一或多对多关系上的非常罕见的情况,可以通过配置relationship.single_parent
参数来强制“多”端一次只允许一个对象,该参数建立了 Python 端验证,确保对象一次只与一个父对象关联,但这大大限制了“多”关系的功能,通常不是所需的。
另请参阅
对于关系,delete-orphan 级联通常仅配置在一对多关系的“一”端,而不是多对一或多对多关系的“多”端。 - 关于涉及 delete-orphan 级联的常见错误场景的背景。 ## merge
merge
级联表示应该从Session.merge()
操作从调用Session.merge()
的主体父对象向下传播到引用的对象。这个级联也是默认开启的。 ## refresh-expire
refresh-expire
是一个不常见的选项,表示Session.refresh()
操作应该从父对象传播到引用的对象。当使用Session.refresh()
时,引用的对象仅被过期,而不会实际刷新。## expunge
expunge
级联表示当使用Session.expunge()
从Session
中移除父对象时,操作应该向下传播到引用的对象。## 删除说明 - 删除从集合和标量关系引用的对象
通常情况下,ORM 在刷新过程中不会修改集合或标量关系的内容。这意味着,如果你的类有一个指向对象集合的relationship()
,或者一个指向单个对象的引用,比如一对多关系,那么当刷新过程发生时,这个属性的内容不会被修改。相反,预期的是Session
最终会过期,要么通过Session.commit()
的提交时过期行为,要么通过显式使用Session.expire()
。在那时,与该Session
关联的任何引用对象或集合将被清除,并且在下次访问时将重新加载自己。
关于此行为常见的混淆涉及 Session.delete()
方法的使用。当调用 Session.delete()
删除一个对象并且刷新了 Session
时,该行将从数据库中删除。通过外键引用目标行的行,假设它们使用两个映射对象类型之间的 relationship()
跟踪,还将看到它们的外键属性被更新为 null,或者如果设置了级联删除,则相关行也将被删除。然而,即使与被删除对象相关的行可能也被修改,在刷新本身的范围内,涉及操作的关系绑定集合或对象引用上不会发生任何更改。这意味着如果对象是相关集合的成员,则在 Python 端它仍然存在,直到该集合过期。同样,如果对象通过多对一或一对一从另一个对象引用,那个引用也将保留在该对象上,直到该对象也过期。
在下面的例子中,我们可以看到,即使将一个 Address
对象标记为删除,在刷新后,它仍然存在于与父 User
关联的集合中:
>>> address = user.addresses[1]
>>> session.delete(address)
>>> session.flush()
>>> address in user.addresses
True
当上述会话提交时,所有属性都将过期。下一次访问 user.addresses
将重新加载集合,显示所需的状态:
>>> session.commit()
>>> address in user.addresses
False
拦截 Session.delete()
并自动调用其过期的方法有一种方法;请参阅 ExpireRelationshipOnFKChange 查看详情。然而,通常的做法是在集合内删除项目时直接放弃使用 Session.delete()
,而是使用级联行为自动调用删除操作,因为将对象从父集合中删除的结果。delete-orphan
级联可以实现这一点,如下例所示:
class User(Base):
__tablename__ = "user"
# ...
addresses = relationship("Address", cascade="all, delete-orphan")
# ...
del user.addresses[1]
session.flush()
在上面的情况中,从 User.addresses
集合中移除 Address
对象后,delete-orphan
级联的效果与将其传递给 Session.delete()
相同,标记了 Address
对象以删除。
delete-orphan
级联也可以应用于多对一或一对一关系,这样当一个对象从其父对象中取消关联时,它也会自动标记为删除。在多对一或一对一关系上使用delete-orphan
级联需要额外的标志relationship.single_parent
,该标志会触发一个断言,即此相关对象不应同时与任何其他父对象共享:
class User(Base):
# ...
preference = relationship(
"Preference", cascade="all, delete-orphan", single_parent=True
)
如果上面的情况下,一个假设的Preference
对象从一个User
中移除,它将在 flush 时被删除:
some_user.preference = None
session.flush() # will delete the Preference object
另请参阅
有关级联的详细信息,请参阅 Cascades。## save-update
save-update
级联表示当通过Session.add()
将对象放入Session
时,通过此relationship()
与之关联的所有对象也应该被添加到同一个Session
中。假设我们有一个对象user1
,它有两个相关对象address1
、address2
:
>>> user1 = User()
>>> address1, address2 = Address(), Address()
>>> user1.addresses = [address1, address2]
如果我们将user1
添加到Session
中,它也会隐式添加address1
、address2
:
>>> sess = Session()
>>> sess.add(user1)
>>> address1 in sess
True
save-update
级联也会影响已经存在于Session
中的对象的属性操作。如果我们向user1.addresses
集合添加第三个对象address3
,它将成为该Session
的状态的一部分:
>>> address3 = Address()
>>> user1.addresses.append(address3)
>>> address3 in sess
True
当从集合中删除项目或将对象与标量属性取消关联时,save-update
级联可能会表现出令人惊讶的行为。在某些情况下,被孤立的对象仍然可能被拉入原父对象的Session
;这是为了 flush 进程能够适当处理该相关对象。这种情况通常只会在对象从一个Session
中移除并添加到另一个Session
时出现:
>>> user1 = sess1.scalars(select(User).filter_by(id=1)).first()
>>> address1 = user1.addresses[0]
>>> sess1.close() # user1, address1 no longer associated with sess1
>>> user1.addresses.remove(address1) # address1 no longer associated with user1
>>> sess2 = Session()
>>> sess2.add(user1) # ... but it still gets added to the new session,
>>> address1 in sess2 # because it's still "pending" for flush
True
save-update
级联默认启用,并且通常被视为理所当然;它通过允许对那个Session
一次注册整个对象结构的单个调用来简化代码。虽然它可以被禁用,但通常没有必要这样做。
具有双向关系的 save-update 级联的行为
save-update
级联在双向关系的情况下单向发生,即当使用relationship.back_populates
或relationship.backref
参数创建两个相互引用的relationship()
对象时。
当将一个未与Session
关联的对象分配给与Session
关联的父对象的属性或集合时,该对象将自动添加到同一个Session
中。然而,反向操作不会产生这种效果;当分配一个未与Session
关联的对象时,分配给一个与Session
关联的子对象,不会自动将父对象添加到Session
中。这种行为的总体主题被称为“级联反向引用”,代表了作为 SQLAlchemy 2.0 的标准化行为的变化。
为了说明,假设有一系列通过关系Order.items
和Item.order
与Item
对象双向关联的Order
对象的映射:
mapper_registry.map_imperatively(
Order,
order_table,
properties={"items": relationship(Item, back_populates="order")},
)
mapper_registry.map_imperatively(
Item,
item_table,
properties={"order": relationship(Order, back_populates="items")},
)
如果Order
已经与一个Session
关联,并且然后创建一个Item
对象并附加到该Order
的Order.items
集合中,Item
将自动级联到相同的Session
中:
>>> o1 = Order()
>>> session.add(o1)
>>> o1 in session
True
>>> i1 = Item()
>>> o1.items.append(i1)
>>> o1 is i1.order
True
>>> i1 in session
True
在上面的例子中,Order.items
和Item.order
的双向性意味着附加到Order.items
也会赋值给Item.order
。同时,save-update
级联允许将Item
对象添加到与父Order
已关联的同一个Session
中。
然而,如果上述操作在反向方向进行,即将Item.order
赋值而不是直接附加到Order.item
,则级联操作不会自动进行到Session
中,即使对象赋值Order.items
和Item.order
的状态与前面的示例相同:
>>> o1 = Order()
>>> session.add(o1)
>>> o1 in session
True
>>> i1 = Item()
>>> i1.order = o1
>>> i1 in order.items
True
>>> i1 in session
False
在上述情况下,在创建Item
对象并设置所有所需状态之后,应明确将其添加到Session
中:
>>> session.add(i1)
在旧版本的 SQLAlchemy 中,保存-更新级 learning method 会在所有情况下双向发生。然后,通过一个名为cascade_backrefs
的选项将其变为可选。最后,在 SQLAlchemy 1.4 中,旧行为被弃用,并且在 SQLAlchemy 2.0 中删除了cascade_backrefs
选项。其理由是,用户通常不会觉得在对象的属性上赋值(如上面所示的i1.order = o1
的赋值)会改变该对象i1
的持久化状态,使其现在处于Session
中处于挂起状态,并且经常会出现自动刷新会过早刷新对象并导致错误的情况,在这些情况下,给定对象仍在构建中且尚未处于准备好刷新的状态状态。选择单向和双向行为之间的选项也被删除,因为此选项创建了两种略有不同的工作方式,增加了 ORM 的整体学习曲线以及文档和用户支持负担。
另请参阅
在 2.0 中弃用了 cascade_backrefs 行为 - 关于“级联 backrefs”行为变更的背景 ### 双向关系中保存-更新级联的行为
save-update
级联在双向关系的上下文中单向发生,即在使用relationship.back_populates
或relationship.backref
参数创建相互引用的两个单独的relationship()
对象时。
一个未与Session
相关联的对象,当分配给与Session
相关联的父对象的属性或集合时,将自动添加到相同的Session
中。但是,相反的操作不会产生这种效果;一个未与Session
相关联的对象,其中一个与Session
相关联的子对象被分配,将不会自动将该父对象添加到Session
中。此行为的整体主题称为“级联反向引用”,并代表了作为 SQLAlchemy 2.0 的标准化行为的变化。
为了说明,假设有一系列通过关系Order.items
和Item.order
与Item
对象双向关联的Order
对象的映射:
mapper_registry.map_imperatively(
Order,
order_table,
properties={"items": relationship(Item, back_populates="order")},
)
mapper_registry.map_imperatively(
Item,
item_table,
properties={"order": relationship(Order, back_populates="items")},
)
如果Order
已与Session
相关联,并且然后创建Item
对象并将其附加到该Order
的Order.items
集合中,那么Item
将自动级联到相同的Session
中:
>>> o1 = Order()
>>> session.add(o1)
>>> o1 in session
True
>>> i1 = Item()
>>> o1.items.append(i1)
>>> o1 is i1.order
True
>>> i1 in session
True
上述案例中,Order.items
和Item.order
的双向性意味着附加到Order.items
也会赋值给Item.order
。同时,save-update
级联允许将Item
对象添加到与父Order
已关联的相同Session
中。
但是,如果上述操作是以相反方向执行的,即将Item.order
赋值而不是直接附加到Order.item
,则即使对象分配Order.items
和Item.order
的状态与前面的示例相同,也不会自动进入到Session
的级联操作中:
>>> o1 = Order()
>>> session.add(o1)
>>> o1 in session
True
>>> i1 = Item()
>>> i1.order = o1
>>> i1 in order.items
True
>>> i1 in session
False
在上述情况下,在创建Item
对象并设置所有所需状态之后,应明确将其添加到Session
中:
>>> session.add(i1)
在较旧版本的 SQLAlchemy 中,保存-更新级联在所有情况下都会双向发生。然后,使用称为cascade_backrefs
的选项将其变为可选。最后,在 SQLAlchemy 1.4 中,旧行为被弃用,并且在 SQLAlchemy 2.0 中删除了cascade_backrefs
选项。其理由是用户通常不会觉得将对象的属性分配给对象上的属性是直观的,如上面所示的i1.order = o1
的赋值会改变对象i1
的持久化状态,使其现在处于Session
中处于挂起状态,并且在那些给定对象仍在构建并且尚未准备好被刷新的情况下,会经常出现自动刷新会过早刷新对象并导致错误的情况。选择单向和双向行为之间的选项也被删除,因为此选项创建了两种略有不同的工作方式,增加了 ORM 的整体学习曲线以及文档和用户支持负担。
另请参阅
2.0 中将删除的 cascade_backrefs 行为已弃用 - 关于“级联反向引用”行为变更的背景信息
删除
删除
级联表示当“父”对象标记为删除时,其相关的“子”对象也应标记为删除。例如,如果我们有一个配置了删除
级联的关系User.addresses
:
class User(Base):
# ...
addresses = relationship("Address", cascade="all, delete")
如果使用上述映射,我们有一个User
对象和两个相关的Address
对象:
>>> user1 = sess1.scalars(select(User).filter_by(id=1)).first()
>>> address1, address2 = user1.addresses
如果我们标记user1
进行删除,在刷新操作进行后,address1
和address2
也将被删除:
>>> sess.delete(user1)
>>> sess.commit()
DELETE FROM address WHERE address.id = ?
((1,), (2,))
DELETE FROM user WHERE user.id = ?
(1,)
COMMIT
或者,如果我们的User.addresses
关系没有删除
级联,SQLAlchemy 的默认行为是通过将它们的外键引用设置为NULL
来解除user1
与address1
和address2
的关联。使用以下映射:
class User(Base):
# ...
addresses = relationship("Address")
在删除父User
对象时,address
中的行不会被删除,而是被解除关联:
>>> sess.delete(user1)
>>> sess.commit()
UPDATE address SET user_id=? WHERE address.id = ?
(None, 1)
UPDATE address SET user_id=? WHERE address.id = ?
(None, 2)
DELETE FROM user WHERE user.id = ?
(1,)
COMMIT
删除 在一对多关系上的级联通常与删除孤儿级联结合使用,如果“子”对象与父对象解除关联,则会发出与相关行相关的 DELETE 操作。删除
和删除孤儿
级联的组合涵盖了 SQLAlchemy 需要在将外键列设置为 NULL 与完全删除行之间做出决定的情况。
默认情况下,该功能完全独立于数据库配置的可能配置CASCADE
行为的FOREIGN KEY
约束。为了更有效地与此配置集成,应使用在使用 ORM 关系的外键 ON DELETE 级联中描述的附加指令。
警告
请注意,ORM 的“删除”和“删除孤立对象”行为仅适用于使用Session.delete()
方法在工作单元过程中标记个别 ORM 实例进行删除。它不适用于“批量”删除,这将使用delete()
构造来发出,如 ORM UPDATE and DELETE with Custom WHERE Criteria 中所示。有关更多背景信息,请参见 ORM-启用的更新和删除的重要说明和注意事项。
另见
使用 ORM 关系的外键 ON DELETE 级联
使用多对多关系的级联删除
delete-orphan
使用多对多关系的级联删除
cascade="all, delete"
选项与多对多关系同样适用,这种关系使用relationship.secondary
来指示一个关联表。当删除父对象时,因此取消与其相关的对象的关联时,工作单元过程通常会从关联表中删除行,但会保留相关的对象。当与cascade="all, delete"
组合时,额外的DELETE
语句将对子行本身进行操作。
下面的示例调整了多对多的例子,以说明关联的一端上的cascade="all, delete"
设置:
association_table = Table(
"association",
Base.metadata,
Column("left_id", Integer, ForeignKey("left.id")),
Column("right_id", Integer, ForeignKey("right.id")),
)
class Parent(Base):
__tablename__ = "left"
id = mapped_column(Integer, primary_key=True)
children = relationship(
"Child",
secondary=association_table,
back_populates="parents",
cascade="all, delete",
)
class Child(Base):
__tablename__ = "right"
id = mapped_column(Integer, primary_key=True)
parents = relationship(
"Parent",
secondary=association_table,
back_populates="children",
)
当使用Session.delete()
标记要删除的Parent
对象时,上述情况下,刷新过程通常会从association
表中删除关联行,但根据级联规则,它还将删除所有相关的Child
行。
警告
如果上述cascade="all, delete"
设置在两个关系上都配置了,则级联操作将继续通过所有Parent
和Child
对象,加载遇到的每个children
和parents
集合,并删除所有连接的内容。通常不希望将“删除”级联配置为双向。
另见
从多对多表中删除行
使用外键 ON DELETE 与多对多关系 ### 使用 ORM 关系的外键 ON DELETE 级联
SQLAlchemy 的“delete”级联行为与数据库FOREIGN KEY
约束的ON DELETE
特性重叠。SQLAlchemy 允许使用ForeignKey
和ForeignKeyConstraint
构造配置这些模式级 DDL 行为;与Table
元数据一起使用这些对象的用法在 ON UPDATE and ON DELETE 中有描述。
为了在与relationship()
一起使用ON DELETE
外键级联时,首先需要注意的是relationship.cascade
设置仍然必须配置为与所需的delete
或“set null”行为匹配(使用delete
级联或将其省略),以便 ORM 或数据库级约束将处理实际修改数据库中数据的任务时,ORM 仍然能够适当跟踪可能受影响的本地存在的对象的状态。
然后,在relationship()
上有一个附加选项,指示 ORM 应该尝试自己运行 DELETE/UPDATE 操作相关行的程度,还是应该依赖于期望数据库端 FOREIGN KEY 约束级联处理任务;这是relationship.passive_deletes
参数,它接受False
(默认值),True
和"all"
选项。
最典型的示例是,在删除父行时删除子行,并且在相关的FOREIGN KEY
约束上配置了ON DELETE CASCADE
:
class Parent(Base):
__tablename__ = "parent"
id = mapped_column(Integer, primary_key=True)
children = relationship(
"Child",
back_populates="parent",
cascade="all, delete",
passive_deletes=True,
)
class Child(Base):
__tablename__ = "child"
id = mapped_column(Integer, primary_key=True)
parent_id = mapped_column(Integer, ForeignKey("parent.id", ondelete="CASCADE"))
parent = relationship("Parent", back_populates="children")
当删除父行时,上述配置的行为如下:
- 应用程序调用
session.delete(my_parent)
,其中my_parent
是Parent
类的一个实例。 - 当
Session
下次将更改刷新到数据库时,my_parent.children
集合中的当前加载的所有项目都将被 ORM 删除,这意味着为每个记录发出一个DELETE
语句。 - 如果
my_parent.children
集合未加载,则不会发出任何DELETE
语句。如果在这个relationship()
上未设置relationship.passive_deletes
标志,则会发出一个用于未加载的Child
对象的SELECT
语句。 - 然后为
my_parent
行本身发出一个DELETE
语句。 - 数据库级别的
ON DELETE CASCADE
设置确保了所有引用受影响的parent
行的child
行也被删除。 - 由
my_parent
引用的Parent
实例,以及与此对象相关且已经加载(即发生了步骤 2)的所有Child
实例,都会从Session
中解除关联。
注意
要使用“ON DELETE CASCADE”,底层数据库引擎必须支持FOREIGN KEY
约束,并且它们必须是强制执行的:
- 当使用 MySQL 时,必须选择适当的存储引擎。详情请参阅包括存储引擎的 CREATE TABLE 参数。
- 当使用 SQLite 时,必须显式启用外键支持。详情请参阅外键支持。### 使用外键 ON DELETE 处理多对多关系
如 使用级联删除处理多对多关系 中所述,“delete”级联也适用于多对多关系。要使用 ON DELETE CASCADE
外键与多对多一起使用,必须在关联表上配置 FOREIGN KEY
指令。这些指令可以处理自动从关联表中删除,但不能自动删除相关对象本身。
在这种情况下,relationship.passive_deletes
指令可以在删除操作期间为我们节省一些额外的 SELECT
语句,但仍然有一些集合 ORM 将继续加载,以便定位受影响的子对象并正确处理它们。
注意
对此的假设优化可能包括一条针对关联表的所有父关联行的单个 DELETE
语句,然后使用 RETURNING
定位受影响的相关子行,但是这目前不是 ORM 工作单元实现的一部分。
在此配置中,我们在关联表的两个外键约束上都配置了 ON DELETE CASCADE
。我们在关系的父->子方向上配置了 cascade="all, delete"
,然后我们可以在双向关系的另一侧上配置 passive_deletes=True
,如下所示:
association_table = Table(
"association",
Base.metadata,
Column("left_id", Integer, ForeignKey("left.id", ondelete="CASCADE")),
Column("right_id", Integer, ForeignKey("right.id", ondelete="CASCADE")),
)
class Parent(Base):
__tablename__ = "left"
id = mapped_column(Integer, primary_key=True)
children = relationship(
"Child",
secondary=association_table,
back_populates="parents",
cascade="all, delete",
)
class Child(Base):
__tablename__ = "right"
id = mapped_column(Integer, primary_key=True)
parents = relationship(
"Parent",
secondary=association_table,
back_populates="children",
passive_deletes=True,
)
使用上述配置,删除 Parent
对象的过程如下:
- 使用
Session.delete()
标记要删除的Parent
对象。 - 当刷新发生时,如果未加载
Parent.children
集合,则 ORM 将首先发出 SELECT 语句以加载与Parent.children
对应的Child
对象。 - 然后会为对应于该父行的
association
中的行发出DELETE
语句。 - 对于由此立即删除受影响的每个
Child
对象,因为配置了passive_deletes=True
,工作单元不需要尝试为每个Child.parents
集合发出 SELECT 语句,因为假设将删除association
中对应的行。 - 然后对从
Parent.children
加载的每个Child
对象发出DELETE
语句。 ### 使用删除级联处理多对多关系
cascade="all, delete"
选项与多对多关系同样有效,即使用 relationship.secondary
指示关联表的关系。当删除父对象并因此取消关联其相关对象时,工作单元进程通常会删除关联表中的行,但保留相关对象。当与 cascade="all, delete"
结合使用时,将为子行本身执行额外的 DELETE
语句。
以下示例将多对多的示例调整为示例,以说明在关联的一侧上设置 cascade="all, delete"
。
association_table = Table(
"association",
Base.metadata,
Column("left_id", Integer, ForeignKey("left.id")),
Column("right_id", Integer, ForeignKey("right.id")),
)
class Parent(Base):
__tablename__ = "left"
id = mapped_column(Integer, primary_key=True)
children = relationship(
"Child",
secondary=association_table,
back_populates="parents",
cascade="all, delete",
)
class Child(Base):
__tablename__ = "right"
id = mapped_column(Integer, primary_key=True)
parents = relationship(
"Parent",
secondary=association_table,
back_populates="children",
)
上面,当使用 Session.delete()
标记要删除的 Parent
对象时,刷新过程将按照惯例从 association
表中删除相关行,但根据级联规则,它还将删除所有相关的 Child
行。
警告
如果上述 cascade="all, delete"
设置被配置在两个关系上,那么级联操作将继续通过所有 Parent
和 Child
对象进行级联,加载遇到的每个 children
和 parents
集合,并删除所有连接的内容。通常不希望双向配置“delete”级联。
另请参阅
从多对多表中删除行
使用外键 ON DELETE 处理多对多关系
使用 ORM 关系中的外键 ON DELETE 级联
SQLAlchemy 的“delete”级联的行为与数据库FOREIGN KEY
约束的ON DELETE
特性重叠。SQLAlchemy 允许使用 ForeignKey
和 ForeignKeyConstraint
构造配置这些模式级别的 DDL 行为;在与 Table
元数据结合使用这些对象的用法在 ON UPDATE and ON DELETE 中有描述。
要在 relationship()
中使用ON DELETE
外键级联,首先要注意的是 relationship.cascade
设置必须仍然配置为匹配所需的“删除”或“设置为 null”行为(使用delete
级联或将其省略),以便 ORM 或数据库级别的约束将处理实际修改数据库中的数据的任务时,ORM 仍将能够适当地跟踪可能受到影响的本地存在的对象的状态。
然后在relationship()
上有一个额外的选项,指示 ORM 应该尝试在相关行上自行运行 DELETE/UPDATE 操作的程度,而不是依靠期望数据库端 FOREIGN KEY 约束级联处理该任务;这是 relationship.passive_deletes
参数,它接受选项 False
(默认值)、True
和 "all"
。
最典型的例子是,在删除父行时要删除子行,并且在相关的FOREIGN KEY
约束上配置了ON DELETE CASCADE
:
class Parent(Base):
__tablename__ = "parent"
id = mapped_column(Integer, primary_key=True)
children = relationship(
"Child",
back_populates="parent",
cascade="all, delete",
passive_deletes=True,
)
class Child(Base):
__tablename__ = "child"
id = mapped_column(Integer, primary_key=True)
parent_id = mapped_column(Integer, ForeignKey("parent.id", ondelete="CASCADE"))
parent = relationship("Parent", back_populates="children")
当删除父行时,上述配置的行为如下:
- 应用程序调用
session.delete(my_parent)
,其中my_parent
是Parent
的实例。 - 当
Session
下次将更改刷新到数据库时,my_parent.children
集合中的所有当前加载的项目都将被 ORM 删除,这意味着为每个记录发出了一个DELETE
语句。 - 如果
my_parent.children
集合未加载,则不会发出DELETE
语句。 如果在此relationship()
上未设置relationship.passive_deletes
标志,那么将会发出一个针对未加载的Child
对象的SELECT
语句。 - 针对
my_parent
行本身发出了一个DELETE
语句。 - 数据库级别的
ON DELETE CASCADE
设置确保了所有引用受影响的parent
行的child
中的行也被删除。 - 由
my_parent
引用的Parent
实例以及所有与此对象相关联且已加载的Child
实例(即发生了步骤 2)都将从Session
中解除关联。
注意
要使用“ON DELETE CASCADE”,底层数据库引擎必须支持FOREIGN KEY
约束,并且它们必须是强制性的:
- 使用 MySQL 时,必须选择适当的存储引擎。 有关详细信息,请参阅 CREATE TABLE arguments including Storage Engines。
- 使用 SQLite 时,必须显式启用外键支持。 有关详细信息,请参阅 Foreign Key Support。
在多对多关系中使用外键 ON DELETE
如使用 delete cascade 与多对多关系所述,“delete”级联也适用于多对多关系。 要利用ON DELETE CASCADE
外键与多对多关系,必须在关联表上配置FOREIGN KEY
指令。 这些指令可以处理自动从关联表中删除,但无法适应相关对象本身的自动删除。
在这种情况下,relationship.passive_deletes
指令可以在删除操作期间为我们节省一些额外的SELECT
语句,但仍然有一些集合是 ORM 将继续加载的,以定位受影响的子对象并正确处理它们。
注意
对此的假设优化可以包括一次针对关联表的所有父关联行的单个DELETE
语句,然后使用RETURNING
来定位受影响的相关子行,但这目前不是 ORM 工作单元实现的一部分。
在此配置中,我们在关联表的两个外键约束上都配置了ON DELETE CASCADE
。我们在关系的父->子方向上配置了cascade="all, delete"
,然后我们可以在双向关系的另一侧上配置passive_deletes=True
,如下所示:
association_table = Table(
"association",
Base.metadata,
Column("left_id", Integer, ForeignKey("left.id", ondelete="CASCADE")),
Column("right_id", Integer, ForeignKey("right.id", ondelete="CASCADE")),
)
class Parent(Base):
__tablename__ = "left"
id = mapped_column(Integer, primary_key=True)
children = relationship(
"Child",
secondary=association_table,
back_populates="parents",
cascade="all, delete",
)
class Child(Base):
__tablename__ = "right"
id = mapped_column(Integer, primary_key=True)
parents = relationship(
"Parent",
secondary=association_table,
back_populates="children",
passive_deletes=True,
)
使用上述配置,删除 Parent
对象的过程如下:
- 使用
Session.delete()
标记要删除的Parent
对象。 - 当发生刷新时,如果未加载
Parent.children
集合,则 ORM 首先会发出 SELECT 语句,以加载与Parent.children
对应的Child
对象。 - 然后会为与该父行对应的
association
中的行发出DELETE
语句。 - 对于受此即时删除影响的每个
Child
对象,因为配置了passive_deletes=True
,工作单元不需要尝试为每个Child.parents
集合发出 SELECT 语句,因为假设将删除association
中的相应行。 - 然后会为从
Parent.children
中加载的每个Child
对象发出DELETE
语句。
删除孤立
delete-orphan
级联为 delete
级联增加了行为,使得当子对象与父对象取消关联时,子对象将被标记为删除,而不仅仅是当父对象被标记为删除时。当处理由其父对象“拥有”的相关对象时,这是一个常见功能,具有非空的外键,以便从父集合中移除项目会导致其删除。
delete-orphan
级联意味着每个子对象一次只能有一个父对象,并且在绝大多数情况下仅配置在一对多关系上。在很少见的情况下,在多对一或多对多关系上设置它,“多”方可以通过配置 relationship.single_parent
参数,强制允许一次只有一个对象与父对象关联,从而在 Python 端建立验证,确保对象一次只与一个父对象关联,但这严重限制了“多”关系的功能,通常不是所期望的。
另请参阅
对于关系,delete-orphan
级联通常仅配置在一对多关系的“一”方,并且不配置在多对一或多对多关系的“多”方。 - 关于涉及 delete-orphan
级联的常见错误场景的背景信息。
合并
merge
级联表示 Session.merge()
操作应从 Session.merge()
调用的主体父对象传播到引用对象。此级联默认也是打开的。
刷新-过期
refresh-expire
是一个不常见的选项,表示Session.expire()
操作应该从父对象传播到引用的对象。当使用Session.refresh()
时,引用的对象只是过期了,而不是实际刷新了。
清除
清除
级联指的是当父对象从Session
中使用Session.expunge()
移除时,该操作应传播到引用的对象。
删除注意事项 - 删除集合和标量关系中引用的对象
通常,ORM 在刷新过程中永远不会修改集合或标量关系的内容。这意味着,如果你的类有一个指向对象集合的relationship()
,或者一个指向单个对象的引用,比如多对一,当刷新过程发生时,这个属性的内容不会被修改。相反,预计Session
最终会过期,通过Session.commit()
的提交时过期行为或通过Session.expire()
的显式使用。在那时,与该Session
相关联的任何引用对象或集合都将被清除,并在下次访问时重新加载自身。
关于这种行为产生的常见困惑涉及到Session.delete()
方法的使用。当在一个对象上调用Session.delete()
并且Session
被刷新时,该行将从数据库中删除。通过外键引用目标行的行,假设它们是使用两个映射对象类型之间的relationship()
进行跟踪的,也会看到它们的外键属性被更新为 null,或者如果设置了删除级联,相关行也将被删除。然而,即使与已删除对象相关的行可能也被修改,在刷新范围内操作的对象上的关系绑定集合或对象引用不会发生任何更改。这意味着如果对象是相关集合的成员,它将仍然存在于 Python 端,直到该集合过期为止。同样,如果对象通过另一个对象的多对一或一对一引用,则该引用也将保留在该对象上,直到该对象也过期为止。
下面,我们说明了在将Address
对象标记为删除后,即使在刷新后,它仍然存在于与父User
关联的集合中:
>>> address = user.addresses[1]
>>> session.delete(address)
>>> session.flush()
>>> address in user.addresses
True
当以上会话提交时,所有属性都会过期。对user.addresses
的下一次访问将重新加载集合,显示所需的状态:
>>> session.commit()
>>> address in user.addresses
False
有一个拦截Session.delete()
并自动调用此过期的配方;参见ExpireRelationshipOnFKChange。然而,删除集合中的项目的通常做法是直接放弃使用Session.delete()
,而是使用级联行为自动调用删除作为从父集合中删除对象的结果。delete-orphan
级联实现了这一点,如下例所示:
class User(Base):
__tablename__ = "user"
# ...
addresses = relationship("Address", cascade="all, delete-orphan")
# ...
del user.addresses[1]
session.flush()
在上面的例子中,当从User.addresses
集合中移除Address
对象时,delete-orphan
级联的效果与将其传递给Session.delete()
相同,都将Address
对象标记为删除。
delete-orphan
级联也可以应用于多对一或一对一关系,这样当一个对象与其父对象解除关联时,它也会被自动标记为删除。在多对一或一对一关系上使用delete-orphan
级联需要一个额外的标志relationship.single_parent
,它调用一个断言,指出这个相关对象不会同时与任何其他父对象共享:
class User(Base):
# ...
preference = relationship(
"Preference", cascade="all, delete-orphan", single_parent=True
)
上面,如果一个假设的Preference
对象从一个User
中移除,它将在刷新时被删除:
some_user.preference = None
session.flush() # will delete the Preference object
另请参阅
级联以获取级联的详细信息。
事务和连接管理
原文:
docs.sqlalchemy.org/en/20/orm/session_transaction.html
事务管理
在 1.4 版本中更改:会话事务管理已经修订为更清晰、更易于使用。特别是,它现在具有“自动开始”操作,这意味着可以控制事务开始的时间点,而不使用传统的“自动提交”模式。
Session
一次跟踪单个“虚拟”事务的状态,使用一个称为SessionTransaction
的对象。然后,此对象利用绑定到Session
对象的基础Engine
或引擎,根据需要使用Connection
对象开始真实的连接级事务。
此“虚拟”事务在需要时会自动创建,或者可以使用Session.begin()
方法启动。尽可能地支持 Python 上下文管理器的使用,既在创建Session
对象的层面,也在维护SessionTransaction
的范围方面。
假设我们从Session
开始:
from sqlalchemy.orm import Session
session = Session(engine)
我们现在可以使用上下文管理器在一个确定的事务中运行操作:
代码语言:javascript复制with session.begin():
session.add(some_object())
session.add(some_other_object())
# commits transaction at the end, or rolls back if there
# was an exception raised
在上下文结束时,假设没有引发任何异常,则任何待处理的对象都将被刷新到数据库,并且数据库事务将被提交。如果在上述块内引发了异常,则事务将被回滚。在这两种情况下,上述Session
在退出块后都已准备好用于后续的事务。
Session.begin()
方法是可选的,Session
也可以使用按需自动开始事务的“随时提交”方法;这些只需要提交或回滚:
session = Session(engine)
session.add(some_object())
session.add(some_other_object())
session.commit() # commits
# will automatically begin again
result = session.execute(text("< some select statement >"))
session.add_all([more_objects, ...])
session.commit() # commits
session.add(still_another_object)
session.flush() # flush still_another_object
session.rollback() # rolls back still_another_object
Session
本身具有一个 Session.close()
方法。如果 Session
是在尚未提交或回滚的事务内开始的,该方法将取消(即回滚)该事务,并清除 Session
对象状态中包含的所有对象。如果使用 Session
的方式不能保证调用 Session.commit()
或 Session.rollback()
(例如,不在上下文管理器或类似结构中),则可以使用 close
方法来确保释放所有资源:
# expunges all objects, releases all transactions unconditionally
# (with rollback), releases all database connections back to their
# engines
session.close()
最后,会话的构建/关闭过程本身也可以通过上下文管理器运行。这是确保 Session
对象使用范围在一个固定块内的最佳方式。首先通过 Session
构造函数进行说明:
with Session(engine) as session:
session.add(some_object())
session.add(some_other_object())
session.commit() # commits
session.add(still_another_object)
session.flush() # flush still_another_object
session.commit() # commits
result = session.execute(text("<some SELECT statement>"))
# remaining transactional state from the .execute() call is
# discarded
同样,sessionmaker
可以以相同的方式使用:
Session = sessionmaker(engine)
with Session() as session:
with session.begin():
session.add(some_object)
# commits
# closes the Session
sessionmaker
本身包含一个 sessionmaker.begin()
方法,允许同时进行两个操作:
with Session.begin() as session:
session.add(some_object)
使用 SAVEPOINT
如果底层引擎支持,可以使用 Session.begin_nested()
方法来划定 SAVEPOINT 事务:
Session = sessionmaker()
with Session.begin() as session:
session.add(u1)
session.add(u2)
nested = session.begin_nested() # establish a savepoint
session.add(u3)
nested.rollback() # rolls back u3, keeps u1 and u2
# commits u1 and u2
每次调用 Session.begin_nested()
,都会在当前数据库事务的范围内向数据库发出一个新的“BEGIN SAVEPOINT”命令(如果尚未开始,则开始一个),并返回一个类型为 SessionTransaction
的对象,代表这个 SAVEPOINT 的句柄。当调用该对象的 .commit()
方法时,会向数据库发出“RELEASE SAVEPOINT”,如果调用 .rollback()
方法,则会发出“ROLLBACK TO SAVEPOINT”。封闭的数据库事务仍在进行中。
Session.begin_nested()
通常用作上下文管理器,可以捕获特定的每个实例错误,并在该事务状态的部分发出回滚,而不会回滚整个事务,如下例所示:
for record in records:
try:
with session.begin_nested():
session.merge(record)
except:
print("Skipped record %s" % record)
session.commit()
当由 Session.begin_nested()
产生的上下文管理器完成时,“提交” savepoint,其中包括刷新所有挂起状态的常规行为。当出现错误时,保存点会被回滚,并且更改的对象的 Session
本地状态会过期。
这种模式非常适用于诸如使用 PostgreSQL 并捕获 IntegrityError
来检测重复行的情况;通常情况下,当出现此类错误时,PostgreSQL 会中止整个事务,但是使用 SAVEPOINT 时,外部事务会得以保留。在下面的示例中,将一系列数据持久化到数据库中,并且偶尔会跳过“重复的主键”记录,而无需回滚整个操作:
from sqlalchemy import exc
with session.begin():
for record in records:
try:
with session.begin_nested():
obj = SomeRecord(id=record["identifier"], name=record["name"])
session.add(obj)
except exc.IntegrityError:
print(f"Skipped record {record} - row already exists")
当调用 Session.begin_nested()
时,Session
首先会将当前所有挂起的状态刷新到数据库;这种情况会无条件发生,不管 Session.autoflush
参数的值是什么,该参数通常用于禁用自动刷新。这种行为的原因是,当此嵌套事务发生回滚时,Session
可以使在 SAVEPOINT 范围内创建的任何内存状态过期,同时确保在刷新这些过期对象时,SAVEPOINT 开始之前的对象图状态将可用于重新从数据库加载。
在 SQLAlchemy 的现代版本中,当由 Session.begin_nested()
启动的 SAVEPOINT 被回滚时,自 SAVEPOINT 创建以来已修改的内存对象状态会过期,但自 SAVEPOINT 开始后未修改的其他对象状态将保持不变。这样,后续操作可以继续使用其它未受影响的数据,而无需从数据库刷新。
另请参见
Connection.begin_nested()
- 核心 SAVEPOINT API ### 会话级别与引擎级别的事务控制
在核心中的Connection
和 ORM 中的_session.Session
具有等效的事务语义,无论是在sessionmaker
与Engine
的级别,还是在Session
与Connection
的级别。以下部分根据以下方案详细说明这些情况:
ORM Core
----------------------------------------- -----------------------------------
sessionmaker Engine
Session Connection
sessionmaker.begin() Engine.begin()
some_session.commit() some_connection.commit()
with some_sessionmaker() as session: with some_engine.connect() as conn:
with some_sessionmaker.begin() as session: with some_engine.begin() as conn:
with some_session.begin_nested() as sp: with some_connection.begin_nested() as sp:
边提交边进行
Session
和Connection
都具有Connection.commit()
和Connection.rollback()
方法。使用 SQLAlchemy 2.0 风格的操作,这些方法在所有情况下都会影响最外层的事务。对于Session
,假定Session.autobegin
保持默认值True
。
Engine
:
engine = create_engine("postgresql psycopg2://user:pass@host/dbname")
with engine.connect() as conn:
conn.execute(
some_table.insert(),
[
{"data": "some data one"},
{"data": "some data two"},
{"data": "some data three"},
],
)
conn.commit()
Session
:
Session = sessionmaker(engine)
with Session() as session:
session.add_all(
[
SomeClass(data="some data one"),
SomeClass(data="some data two"),
SomeClass(data="some data three"),
]
)
session.commit()
一次开始
sessionmaker
和Engine
都具有Engine.begin()
方法,该方法将获取一个新对象来执行 SQL 语句(分别是Session
和Connection
),然后返回一个上下文管理器,用于维护该对象的开始/提交/回滚上下文。
引擎:
代码语言:javascript复制engine = create_engine("postgresql psycopg2://user:pass@host/dbname")
with engine.begin() as conn:
conn.execute(
some_table.insert(),
[
{"data": "some data one"},
{"data": "some data two"},
{"data": "some data three"},
],
)
# commits and closes automatically
会话:
代码语言:javascript复制Session = sessionmaker(engine)
with Session.begin() as session:
session.add_all(
[
SomeClass(data="some data one"),
SomeClass(data="some data two"),
SomeClass(data="some data three"),
]
)
# commits and closes automatically
嵌套事务
使用 SAVEPOINT 通过 Session.begin_nested()
或 Connection.begin_nested()
方法时,必须使用返回的事务对象来提交或回滚 SAVEPOINT。 调用 Session.commit()
或 Connection.commit()
方法将始终提交最外层事务;这是 SQLAlchemy 2.0 特定的行为,与 1.x 系列相反。
引擎:
代码语言:javascript复制engine = create_engine("postgresql psycopg2://user:pass@host/dbname")
with engine.begin() as conn:
savepoint = conn.begin_nested()
conn.execute(
some_table.insert(),
[
{"data": "some data one"},
{"data": "some data two"},
{"data": "some data three"},
],
)
savepoint.commit() # or rollback
# commits automatically
会话:
代码语言:javascript复制Session = sessionmaker(engine)
with Session.begin() as session:
savepoint = session.begin_nested()
session.add_all(
[
SomeClass(data="some data one"),
SomeClass(data="some data two"),
SomeClass(data="some data three"),
]
)
savepoint.commit() # or rollback
# commits automatically
```### 显式开始
`Session` 具有“自动开始”行为,这意味着一旦操作开始进行,它就会确保存在一个 `SessionTransaction` 来跟踪正在进行的操作。 当调用 `Session.commit()` 时,此事务将完成。
通常希望,特别是在框架集成中,控制“开始”操作发生的时间点。 为此,`Session` 使用“自动开始”策略,使得可以直接调用 `Session.begin()` 方法,以便为尚未启动事务的 `Session` 调用:
```py
Session = sessionmaker(bind=engine)
session = Session()
session.begin()
try:
item1 = session.get(Item, 1)
item2 = session.get(Item, 2)
item1.foo = "bar"
item2.bar = "foo"
session.commit()
except:
session.rollback()
raise
上述模式更常用地使用上下文管理器调用:
代码语言:javascript复制Session = sessionmaker(bind=engine)
session = Session()
with session.begin():
item1 = session.get(Item, 1)
item2 = session.get(Item, 2)
item1.foo = "bar"
item2.bar = "foo"
Session.begin()
方法和会话的“自动开始”过程使用相同的步骤序列开始事务。 这包括在发生时调用 SessionEvents.after_transaction_create()
事件;此挂钩被框架用于将其自己的事务处理过程与 ORM Session
集成。
对于支持两阶段操作的后端(目前支持 MySQL 和 PostgreSQL),会话可以被指示使用两阶段提交语义。这将协调跨数据库的事务提交,以便在所有数据库中要么提交事务,要么回滚事务。您还可以Session.prepare()
会话以与 SQLAlchemy 不管理的事务进行交互。要使用两阶段事务,请在会话上设置标志 twophase=True
:
engine1 = create_engine("postgresql psycopg2://db1")
engine2 = create_engine("postgresql psycopg2://db2")
Session = sessionmaker(twophase=True)
# bind User operations to engine 1, Account operations to engine 2
Session.configure(binds={User: engine1, Account: engine2})
session = Session()
# .... work with accounts and users
# commit. session will issue a flush to all DBs, and a prepare step to all DBs,
# before committing both transactions
session.commit()
```### 设置事务隔离级别 / DBAPI 自动提交
大多数 DBAPI 支持可配置的事务隔离级别的概念。传统上有四个级别:“READ UNCOMMITTED”,“READ COMMITTED”,“REPEATABLE READ” 和 “SERIALIZABLE”。这些通常应用于 DBAPI 连接在开始新事务之前,注意大多数 DBAPI 在首次发出 SQL 语句时会隐式开始此事务。
支持隔离级别的 DBAPI 通常也支持真正的 “自动提交” 概念,这意味着 DBAPI 连接本身将被置于非事务自动提交模式。这通常意味着数据库自动发出 “BEGIN” 的典型 DBAPI 行为不再发生,但它也可能包括其他指令。在使用此模式时,**DBAPI 在任何情况下都不使用事务**。SQLAlchemy 方法如 `.begin()`、`.commit()` 和 `.rollback()` 将静默传递。
SQLAlchemy 的方言支持在每个 `Engine` 或每个 `Connection` 基础上设置可设置的隔离模式,使用 `create_engine()` 层级以及 `Connection.execution_options()` 层级的标志。
当使用 ORM `Session` 时,它充当引擎和连接的 *facade*,但不直接暴露事务隔离。因此,为了影响事务隔离级别,我们需要根据情况对 `Engine` 或 `Connection` 采取行动。
另请参阅
设置事务隔离级别包括 DBAPI 自动提交 - 请确保查看 SQLAlchemy `Connection` 对象的隔离级别工作方式。
#### 为 Sessionmaker / Engine 设置隔离
要为特定的隔离级别全局设置`Session`或`sessionmaker`,第一种技术是可以针对所有情况构建一个具有特定隔离级别的`Engine`,然后将其用作`Session`和/或`sessionmaker`的连接源:
```py
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
eng = create_engine(
"postgresql psycopg2://scott:tiger@localhost/test",
isolation_level="REPEATABLE READ",
)
Session = sessionmaker(eng)
另一个选项,如果同时存在具有不同隔离级别的两个引擎,是使用Engine.execution_options()
方法,该方法将生成一个原始Engine
的浅拷贝,该浅拷贝与父引擎共享相同的连接池。当操作将被分成“事务”和“自动提交”操作时,这通常是更可取的:
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
eng = create_engine("postgresql psycopg2://scott:tiger@localhost/test")
autocommit_engine = eng.execution_options(isolation_level="AUTOCOMMIT")
transactional_session = sessionmaker(eng)
autocommit_session = sessionmaker(autocommit_engine)
在上述示例中,“eng
”和"autocommit_engine"
共享相同的方言和连接池。然而,当从autocommit_engine
获取连接时,将设置“AUTOCOMMIT”模式。这两个sessionmaker
对象“transactional_session
”和“autocommit_session
”在与数据库连接工作时继承这些特性。
“autocommit_session
” 仍然具有事务语义,包括Session.commit()
和Session.rollback()
仍然认为自己在“提交”和“回滚”对象,然而事务将会默默地不存在。因此,通常情况下,尽管不是严格要求,使用 AUTOCOMMIT 隔离的会话应该以只读方式使用,即:
with autocommit_session() as session:
some_objects = session.execute(text("<statement>"))
some_other_objects = session.execute(text("<statement>"))
# closes connection
设置单个会话的隔离级别
当我们创建一个新的Session
,可以直接使用构造函数,也可以在调用由sessionmaker
生成的可调用对象时,直接传递bind
参数,覆盖预先存在的绑定。例如,我们可以从默认的sessionmaker
创建我们的Session
并传递一个设置为自动提交的引擎:
plain_engine = create_engine("postgresql psycopg2://scott:tiger@localhost/test")
autocommit_engine = plain_engine.execution_options(isolation_level="AUTOCOMMIT")
# will normally use plain_engine
Session = sessionmaker(plain_engine)
# make a specific Session that will use the "autocommit" engine
with Session(bind=autocommit_engine) as session:
# work with session
...
对于Session
或sessionmaker
配置了多个binds
的情况,我们可以重新完整指定binds
参数,或者如果我们只想替换特定的 binds,则可以使用Session.bind_mapper()
或Session.bind_table()
方法:
with Session() as session:
session.bind_mapper(User, autocommit_engine)
为个别事务设置隔离级别
关于隔离级别的一个关键警告是,在已经开始事务的Connection
上不能安全地修改设置。数据库不能在进行中的事务中更改隔离级别,而一些 DBAPIs 和 SQLAlchemy 方言在这方面的行为不一致。
因此,最好使用一个提前绑定到具有所需隔离级别的引擎的Session
。然而,通过在事务开始时使用Session.connection()
方法可以影响每个连接的隔离级别:
from sqlalchemy.orm import Session
# assume session just constructed
sess = Session(bind=engine)
# call connection() with options before any other operations proceed.
# this will procure a new connection from the bound engine and begin a real
# database transaction.
sess.connection(execution_options={"isolation_level": "SERIALIZABLE"})
# ... work with session in SERIALIZABLE isolation level...
# commit transaction. the connection is released
# and reverted to its previous isolation level.
sess.commit()
# subsequent to commit() above, a new transaction may be begun if desired,
# which will proceed with the previous default isolation level unless
# it is set again.
在上面的例子中,我们首先使用构造函数或sessionmaker
生成一个Session
。然后,我们通过调用Session.connection()
显式设置数据库级事务的开始,该方法提供了在数据库级事务开始之前将传递给连接的执行选项。事务使用此选定的隔离级别进行。当事务完成时,隔离级别会重置为其默认值,然后将连接返回到连接池。
Session.begin()
方法也可以用于开始Session
级事务;在调用该方法后,可以使用Session.connection()
来设置每个连接的事务隔离级别:
sess = Session(bind=engine)
with sess.begin():
# call connection() with options before any other operations proceed.
# this will procure a new connection from the bound engine and begin a
# real database transaction.
sess.connection(execution_options={"isolation_level": "SERIALIZABLE"})
# ... work with session in SERIALIZABLE isolation level...
# outside the block, the transaction has been committed. the connection is
# released and reverted to its previous isolation level.
使用事件跟踪事务状态
请参阅事务事件部分,了解有关会话事务状态更改的可用事件挂钩的概述。 ## 加入会话到外部事务(例如用于测试套件)
如果正在使用处于事务状态的 Connection
(即已建立 Transaction
),则可以通过将 Session
绑定到该 Connection
来使 Session
参与该事务。通常的理由是允许 ORM 代码自由地与 Session
一起工作,包括调用 Session.commit()
,之后整个数据库交互都被回滚。
在 2.0 版本中更改:2.0 版本再次对“加入到外部事务”配方进行了改进;不再需要事件处理程序来“重置”嵌套事务。
该配方的工作方式是在事务内部建立一个 Connection
,可选地建立一个 SAVEPOINT,然后将其传递给 Session
作为“bind”;Session.join_transaction_mode
参数传递了设置为 "create_savepoint"
,表示应该创建新的 SAVEPOINT 来实现 Session
的 BEGIN/COMMIT/ROLLBACK,这将使外部事务处于传递时的相同状态。
当测试拆解时,外部事务会被回滚,以便将测试中的任何数据更改还原:
代码语言:javascript复制from sqlalchemy.orm import sessionmaker
from sqlalchemy import create_engine
from unittest import TestCase
# global application scope. create Session class, engine
Session = sessionmaker()
engine = create_engine("postgresql psycopg2://...")
class SomeTest(TestCase):
def setUp(self):
# connect to the database
self.connection = engine.connect()
# begin a non-ORM transaction
self.trans = self.connection.begin()
# bind an individual Session to the connection, selecting
# "create_savepoint" join_transaction_mode
self.session = Session(
bind=self.connection, join_transaction_mode="create_savepoint"
)
def test_something(self):
# use the session in tests.
self.session.add(Foo())
self.session.commit()
def test_something_with_rollbacks(self):
self.session.add(Bar())
self.session.flush()
self.session.rollback()
self.session.add(Foo())
self.session.commit()
def tearDown(self):
self.session.close()
# rollback - everything that happened with the
# Session above (including calls to commit())
# is rolled back.
self.trans.rollback()
# return connection to the Engine
self.connection.close()
上述配方是 SQLAlchemy 自己的 CI 的一部分,以确保它仍然按预期工作。 ## 管理事务
在 1.4 版本中更改:会话事务管理已经进行了修改,使其更清晰、更易于使用。特别是,现在它具有“自动开始”操作,这意味着可以控制事务开始的时间点,而无需使用传统的“自动提交”模式。
Session
跟踪一次性的“虚拟”事务的状态,使用一个叫做 SessionTransaction
的对象。然后,该对象利用底层的 Engine
或引擎来启动使用 Connection
对象所需的真实连接级事务。
当需要时,这个“虚拟”事务会自动创建,或者可以使用 Session.begin()
方法手动开始。尽可能大程度地支持 Python 上下文管理器的使用,不仅在创建 Session
对象的级别上,还在维护 SessionTransaction
的范围上。
下面,假设我们从一个 Session
开始:
from sqlalchemy.orm import Session
session = Session(engine)
现在我们可以使用上下文管理器在标记的事务中运行操作:
代码语言:javascript复制with session.begin():
session.add(some_object())
session.add(some_other_object())
# commits transaction at the end, or rolls back if there
# was an exception raised
在上述上下文结束时,假设没有引发任何异常,任何待处理的对象都将被刷新到数据库,并且数据库事务将被提交。如果在上述块中引发了异常,则事务将被回滚。在这两种情况下,上述 Session
在退出块后都可以在后续事务中使用。
Session.begin()
方法是可选的,Session
也可以使用逐步提交的方法,在需要时自动开始事务;只需提交或回滚:
session = Session(engine)
session.add(some_object())
session.add(some_other_object())
session.commit() # commits
# will automatically begin again
result = session.execute(text("< some select statement >"))
session.add_all([more_objects, ...])
session.commit() # commits
session.add(still_another_object)
session.flush() # flush still_another_object
session.rollback() # rolls back still_another_object
Session
本身具有Session.close()
方法。如果Session
是在尚未提交或回滚的事务内开始的,则此方法将取消(即回滚)该事务,并且还将清除Session
对象状态中包含的所有对象。如果Session
的使用方式不保证调用Session.commit()
或Session.rollback()
(例如不在上下文管理器或类似位置),则可以使用close
方法确保释放所有资源:
# expunges all objects, releases all transactions unconditionally
# (with rollback), releases all database connections back to their
# engines
session.close()
最后,会话构建/关闭过程本身也可以通过上下文管理器运行。这是确保Session
对象使用范围在固定块内的最佳方法。首先通过Session
构造函数进行说明:
with Session(engine) as session:
session.add(some_object())
session.add(some_other_object())
session.commit() # commits
session.add(still_another_object)
session.flush() # flush still_another_object
session.commit() # commits
result = session.execute(text("<some SELECT statement>"))
# remaining transactional state from the .execute() call is
# discarded
同样,sessionmaker
也可以以相同的方式使用:
Session = sessionmaker(engine)
with Session() as session:
with session.begin():
session.add(some_object)
# commits
# closes the Session
sessionmaker
本身包括一个sessionmaker.begin()
方法,允许同时执行这两个操作:
with Session.begin() as session:
session.add(some_object)
使用 SAVEPOINT
如果底层引擎支持 SAVEPOINT 事务,则可以使用Session.begin_nested()
方法来区分 SAVEPOINT 事务:
Session = sessionmaker()
with Session.begin() as session:
session.add(u1)
session.add(u2)
nested = session.begin_nested() # establish a savepoint
session.add(u3)
nested.rollback() # rolls back u3, keeps u1 and u2
# commits u1 and u2
每次调用Session.begin_nested()
时,都会在当前数据库事务的范围内(如果尚未开始,则开始一个新的事务)向数据库发送新的“BEGIN SAVEPOINT”命令,并返回一个类型为SessionTransaction
的对象,该对象表示对此 SAVEPOINT 的句柄。当调用该对象的.commit()
方法时,将向数据库发出“RELEASE SAVEPOINT”命令;如果调用.rollback()
方法,则发出“ROLLBACK TO SAVEPOINT”命令。封闭的数据库事务保持进行中。
Session.begin_nested()
通常用作上下文管理器,其中可以捕获特定的每个实例错误,与事务状态的部分回滚一起使用,而不是回滚整个事务,如下例所示:
for record in records:
try:
with session.begin_nested():
session.merge(record)
except:
print("Skipped record %s" % record)
session.commit()
当由Session.begin_nested()
生成的上下文管理器完成时,它“提交”了保存点,其中包括刷新所有待定状态的通常行为。当发生错误时,保存点被回滚,并且被更改的对象的Session
本地状态会被过期。
这种模式非常适合诸如使用 PostgreSQL 并捕获IntegrityError
以检测重复行的情况;当出现此类错误时,PostgreSQL 通常会中止整个事务,但是在使用 SAVEPOINT 时,外部事务会被维持。在下面的示例中,一组数据被持久化到数据库中,偶尔会跳过“重复的主键”记录,而不会回滚整个操作:
from sqlalchemy import exc
with session.begin():
for record in records:
try:
with session.begin_nested():
obj = SomeRecord(id=record["identifier"], name=record["name"])
session.add(obj)
except exc.IntegrityError:
print(f"Skipped record {record} - row already exists")
当调用Session.begin_nested()
时,Session
首先将当前所有待定状态刷新到数据库;这是无条件发生的,不管Session.autoflush
参数的值如何,该参数通常用于禁用自动刷新。这种行为的理由是,当在这个嵌套事务上发生回滚时,Session
可以使在 SAVEPOINT 范围内创建的任何内存状态过期,同时确保当这些过期对象被刷新时,SAVEPOINT 开始之前的对象图状态可重新从数据库加载。
在现代版本的 SQLAlchemy 中,当由Session.begin_nested()
发起的 SAVEPOINT 被回滚时,自从创建 SAVEPOINT 以来被修改的内存对象状态会被过期,然而自 SAVEPOINT 开始以来未被改变的其他对象状态会被保留。这样,后续操作可以继续使用未受影响的数据,而无需从数据库中刷新。
另请参阅
Connection.begin_nested()
- 核心 SAVEPOINT API ### 会话级别 vs. 引擎级别的事务控制
在核心中的连接
和 ORM 中的_session.Session
具有等效的事务语义,都在sessionmaker
与引擎
的级别,以及会话
与连接
的级别。以下各节根据以下方案详细说明了这些情况:
ORM Core
----------------------------------------- -----------------------------------
sessionmaker Engine
Session Connection
sessionmaker.begin() Engine.begin()
some_session.commit() some_connection.commit()
with some_sessionmaker() as session: with some_engine.connect() as conn:
with some_sessionmaker.begin() as session: with some_engine.begin() as conn:
with some_session.begin_nested() as sp: with some_connection.begin_nested() as sp:
边做边提交
会话
和连接
都具有Connection.commit()
和Connection.rollback()
方法。使用 SQLAlchemy 2.0 风格的操作,这些方法在所有情况下都会影响最外层的事务。对于会话
,假定Session.autobegin
保持默认值True
。
引擎
:
engine = create_engine("postgresql psycopg2://user:pass@host/dbname")
with engine.connect() as conn:
conn.execute(
some_table.insert(),
[
{"data": "some data one"},
{"data": "some data two"},
{"data": "some data three"},
],
)
conn.commit()
会话
:
Session = sessionmaker(engine)
with Session() as session:
session.add_all(
[
SomeClass(data="some data one"),
SomeClass(data="some data two"),
SomeClass(data="some data three"),
]
)
session.commit()
单次开始
sessionmaker
和引擎
都具有Engine.begin()
方法,该方法将获取一个用于执行 SQL 语句的新对象(分别是会话
和连接
),然后返回一个上下文管理器,该管理器将为该对象维护一个开始/提交/回滚的上下文。
引擎:
代码语言:javascript复制engine = create_engine("postgresql psycopg2://user:pass@host/dbname")
with engine.begin() as conn:
conn.execute(
some_table.insert(),
[
{"data": "some data one"},
{"data": "some data two"},
{"data": "some data three"},
],
)
# commits and closes automatically
会话:
代码语言:javascript复制Session = sessionmaker(engine)
with Session.begin() as session:
session.add_all(
[
SomeClass(data="some data one"),
SomeClass(data="some data two"),
SomeClass(data="some data three"),
]
)
# commits and closes automatically
嵌套事务
当使用 SAVEPOINT 通过 Session.begin_nested()
或 Connection.begin_nested()
方法时,返回的事务对象必须用于提交或回滚 SAVEPOINT。调用 Session.commit()
或 Connection.commit()
方法将始终提交最外层事务;这是 SQLAlchemy 2.0 特定于行为的,与 1.x 系列相反。
引擎:
代码语言:javascript复制engine = create_engine("postgresql psycopg2://user:pass@host/dbname")
with engine.begin() as conn:
savepoint = conn.begin_nested()
conn.execute(
some_table.insert(),
[
{"data": "some data one"},
{"data": "some data two"},
{"data": "some data three"},
],
)
savepoint.commit() # or rollback
# commits automatically
会话:
代码语言:javascript复制Session = sessionmaker(engine)
with Session.begin() as session:
savepoint = session.begin_nested()
session.add_all(
[
SomeClass(data="some data one"),
SomeClass(data="some data two"),
SomeClass(data="some data three"),
]
)
savepoint.commit() # or rollback
# commits automatically
```### 显式开始
`Session` 具有“自动开始”行为,这意味着一旦操作开始进行,它就会确保存在一个用于跟踪正在进行的操作的 `SessionTransaction`。当调用 `Session.commit()` 时,该事务将被完成。
通常希望特别是在框架集成中,控制“开始”操作发生的时机。为此,`Session` 使用“自动开始”策略,使得可以直接调用 `Session.begin()` 方法来为尚未启动事务的 `Session` 开始事务:
```py
Session = sessionmaker(bind=engine)
session = Session()
session.begin()
try:
item1 = session.get(Item, 1)
item2 = session.get(Item, 2)
item1.foo = "bar"
item2.bar = "foo"
session.commit()
except:
session.rollback()
raise
上述模式更惯用地使用上下文管理器调用:
代码语言:javascript复制Session = sessionmaker(bind=engine)
session = Session()
with session.begin():
item1 = session.get(Item, 1)
item2 = session.get(Item, 2)
item1.foo = "bar"
item2.bar = "foo"
Session.begin()
方法和会话的“自动开始”过程使用相同的步骤序列开始事务。这包括当 SessionEvents.after_transaction_create()
事件发生时调用;此钩子被框架用于将其自己的事务处理过程与 ORM Session
集成。 ### 启用两阶段提交
对于支持两阶段操作的后端(当前为 MySQL 和 PostgreSQL),可以指示会话使用两阶段提交语义。这将协调跨数据库的事务提交,以便在所有数据库中提交或回滚事务。您还可以Session.prepare()
会话以与 SQLAlchemy 未管理的事务进行交互。要使用两阶段事务,请在会话上设置标志 twophase=True
:
engine1 = create_engine("postgresql psycopg2://db1")
engine2 = create_engine("postgresql psycopg2://db2")
Session = sessionmaker(twophase=True)
# bind User operations to engine 1, Account operations to engine 2
Session.configure(binds={User: engine1, Account: engine2})
session = Session()
# .... work with accounts and users
# commit. session will issue a flush to all DBs, and a prepare step to all DBs,
# before committing both transactions
session.commit()
```### 设置事务隔离级别 / DBAPI AUTOCOMMIT
大多数 DBAPI 支持可配置的事务隔离级别概念。传统上有四个级别:“READ UNCOMMITTED”、“READ COMMITTED”、“REPEATABLE READ”和“SERIALIZABLE”。这些通常在 DBAPI 连接开始新事务之前应用,需要注意的是,大多数 DBAPI 在首次发出 SQL 语句时会隐式开始此事务。
支持隔离级别的 DBAPI 通常还支持真实的“自动提交”概念,这意味着 DBAPI 连接本身将被放置在非事务自动提交模式中。这通常意味着自动向数据库发出“BEGIN”的典型 DBAPI 行为不再发生,但也可能包括其他指令。在使用此模式时,**DBAPI 在任何情况下都不使用事务**。SQLAlchemy 方法如`.begin()`、`.commit()` 和 `.rollback()` 会静默通过。
SQLAlchemy 的方言支持在每个`Engine` 或每个`Connection` 的基础上设置隔离模式,使用`create_engine()` 层次上以及 `Connection.execution_options()` 层次上的标志。
当使用 ORM `Session` 时,它充当引擎和连接的*外观*,但不直接暴露事务隔离。因此,为了影响事务隔离级别,我们需要在适当时对`Engine` 或 `Connection` 进行操作。
另请参阅
设置事务隔离级别,包括 DBAPI 自动提交 - 一定要查看 SQLAlchemy `Connection` 对象级别的隔离级别是如何工作的。
#### 为 Sessionmaker / Engine 设置隔离级别
要为 `Session` 或 `sessionmaker` 设置特定的隔离级别,全局首选技巧是可以始终根据特定的隔离级别构建一个 `Engine`,然后将其用作 `Session` 和/或 `sessionmaker` 的连接源:
```py
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
eng = create_engine(
"postgresql psycopg2://scott:tiger@localhost/test",
isolation_level="REPEATABLE READ",
)
Session = sessionmaker(eng)
另一个选项,如果同时存在具有不同隔离级别的两个引擎,是使用 Engine.execution_options()
方法,该方法将产生原始 Engine
的浅拷贝,该拷贝与父引擎共享相同的连接池。当操作将被分为“事务性”和“自动提交”操作时,通常最好使用此方法:
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
eng = create_engine("postgresql psycopg2://scott:tiger@localhost/test")
autocommit_engine = eng.execution_options(isolation_level="AUTOCOMMIT")
transactional_session = sessionmaker(eng)
autocommit_session = sessionmaker(autocommit_engine)
在上述情况中,“eng
” 和 "autocommit_engine"
共享相同的方言和连接池。但是,当从 autocommit_engine
获取连接时,将设置“AUTOCOMMIT”模式。当这两个 sessionmaker
对象“transactional_session
” 和 “autocommit_session"
与数据库连接一起工作时,它们会继承这些特性。
“autocommit_session
” 仍然具有事务语义,包括 Session.commit()
和 Session.rollback()
仍然将其自己视为“提交”和“回滚”对象,但是事务将会默默消失。因此,通常情况下,虽然不是严格要求,但 AUTOCOMMIT 隔离级别的会话应该以只读方式使用,也就是:
with autocommit_session() as session:
some_objects = session.execute(text("<statement>"))
some_other_objects = session.execute(text("<statement>"))
# closes connection
设置单独会话的隔离级别
当我们创建一个新的 Session
,无论是直接使用构造函数还是调用由 sessionmaker
生成的可调用函数时,我们都可以直接传递 bind
参数,覆盖预先存在的绑定。例如,我们可以从默认的 sessionmaker
创建我们的 Session
,并传递一个设置为自动提交的引擎:
plain_engine = create_engine("postgresql psycopg2://scott:tiger@localhost/test")
autocommit_engine = plain_engine.execution_options(isolation_level="AUTOCOMMIT")
# will normally use plain_engine
Session = sessionmaker(plain_engine)
# make a specific Session that will use the "autocommit" engine
with Session(bind=autocommit_engine) as session:
# work with session
...
对于配置了多个“绑定”(Session
或sessionmaker
)的情况,我们可以重新完全指定binds
参数,或者如果我们只想替换特定的绑定,则可以使用Session.bind_mapper()
或Session.bind_table()
方法:
with Session() as session:
session.bind_mapper(User, autocommit_engine)
为单个事务设置隔离级别
关于隔离级别的一个关键注意事项是,不能在已经开始事务的Connection
上安全地修改设置。数据库不能更改正在进行的事务的隔离级别,并且一些 DBAPIs 和 SQLAlchemy 方言在这个领域的行为不一致。
因此,最好使用一个与所需隔离级别的引擎直接绑定的Session
。然而,可以通过在事务开始时使用Session.connection()
方法来影响每个连接的隔离级别:
from sqlalchemy.orm import Session
# assume session just constructed
sess = Session(bind=engine)
# call connection() with options before any other operations proceed.
# this will procure a new connection from the bound engine and begin a real
# database transaction.
sess.connection(execution_options={"isolation_level": "SERIALIZABLE"})
# ... work with session in SERIALIZABLE isolation level...
# commit transaction. the connection is released
# and reverted to its previous isolation level.
sess.commit()
# subsequent to commit() above, a new transaction may be begun if desired,
# which will proceed with the previous default isolation level unless
# it is set again.
在上面的例子中,我们首先使用构造函数或sessionmaker
生成一个Session
。然后,我们通过调用Session.connection()
显式设置数据库级别事务的开始,该方法提供了将传递给连接的执行选项,在开始数据库级别事务之前进行设置。事务使用所选的隔离级别进行。事务完成后,将在将连接返回到连接池之前将连接上的隔离级别重置为其默认值。
Session.begin()
方法也可以用于开始Session
级别的事务;在调用该方法后,可以使用Session.connection()
设置每个连接的事务隔离级别:
sess = Session(bind=engine)
with sess.begin():
# call connection() with options before any other operations proceed.
# this will procure a new connection from the bound engine and begin a
# real database transaction.
sess.connection(execution_options={"isolation_level": "SERIALIZABLE"})
# ... work with session in SERIALIZABLE isolation level...
# outside the block, the transaction has been committed. the connection is
# released and reverted to its previous isolation level.
使用事件跟踪事务状态
请参阅事务事件部分,了解会话事务状态更改的可用事件挂钩的概述。
使用保存点
如果底层引擎支持 SAVEPOINT 事务,则可以使用Session.begin_nested()
方法进行分割:
Session = sessionmaker()
with Session.begin() as session:
session.add(u1)
session.add(u2)
nested = session.begin_nested() # establish a savepoint
session.add(u3)
nested.rollback() # rolls back u3, keeps u1 and u2
# commits u1 and u2
每次调用Session.begin_nested()
时,都会在当前数据库事务的范围内(如果尚未开始,则开始一个)向数据库发出新的“BEGIN SAVEPOINT”命令,并返回一个SessionTransaction
类型的对象,该对象表示对此保存点的句柄。当调用此对象的.commit()
方法时,将向数据库发出“RELEASE SAVEPOINT”,如果调用.rollback()
方法,则会发出“ROLLBACK TO SAVEPOINT”。外层数据库事务仍然在进行中。
Session.begin_nested()
通常用作上下文管理器,其中可以捕获特定的每个实例错误,在此事务状态的一部分上发出回滚,而无需回滚整个事务,就像下面的示例中一样:
for record in records:
try:
with session.begin_nested():
session.merge(record)
except:
print("Skipped record %s" % record)
session.commit()
当由Session.begin_nested()
生成的上下文管理器完成时,它会“提交”保存点,其中包括刷新所有待处理状态的通常行为。当出现错误时,保存点会被回滚,并且对已更改的对象的Session
的状态将被过期。
此模式非常适合于使用 PostgreSQL 并捕获IntegrityError
以检测重复行的情况;当引发此类错误时,PostgreSQL 通常会中止整个事务,但是当使用 SAVEPOINT 时,外部事务会得以保留。在下面的示例中,将一系列数据持久化到数据库中,偶尔会跳过“重复主键”记录,而不会回滚整个操作:
from sqlalchemy import exc
with session.begin():
for record in records:
try:
with session.begin_nested():
obj = SomeRecord(id=record["identifier"], name=record["name"])
session.add(obj)
except exc.IntegrityError:
print(f"Skipped record {record} - row already exists")
当调用Session.begin_nested()
时,Session
首先会将当前所有待定状态刷新到数据库;无论Session.autoflush
参数的值是什么,这都是无条件的,通常可以用来禁用自动刷新。这种行为的原因是当此嵌套事务上发生回滚时,Session
可以使在保存点范围内创建的任何内存状态过期,同时确保在刷新这些过期对象时,保存点开始前的对象图状态将可用于重新从数据库加载。
在现代版本的 SQLAlchemy 中,当由Session.begin_nested()
初始化的保存点被回滚时,自从保存点创建以来被修改的内存对象状态将会被过期,但是其他自保存点开始时未改变的对象状态将会被保留。这样做是为了让后续操作可以继续使用那些未受影响的数据,而无需从数据库中刷新。
另请参阅
Connection.begin_nested()
- 核心保存点 API
会话级与引擎级事务控制
Core 中的Connection
和 ORM 中的_session.Session
都具有等效的事务语义,无论是在sessionmaker
与Engine
之间,还是在Session
与Connection
之间。以下各节详细说明了这些情景,基于以下方案:
ORM Core
----------------------------------------- -----------------------------------
sessionmaker Engine
Session Connection
sessionmaker.begin() Engine.begin()
some_session.commit() some_connection.commit()
with some_sessionmaker() as session: with some_engine.connect() as conn:
with some_sessionmaker.begin() as session: with some_engine.begin() as conn:
with some_session.begin_nested() as sp: with some_connection.begin_nested() as sp:
边做边提交
Session
和 Connection
均提供了 Connection.commit()
和 Connection.rollback()
方法。使用 SQLAlchemy 2.0 风格的操作时,这些方法在所有情况下都会影响最外层的事务。对于 Session
,假定 Session.autobegin
保持默认值 True
。
Engine
:
engine = create_engine("postgresql psycopg2://user:pass@host/dbname")
with engine.connect() as conn:
conn.execute(
some_table.insert(),
[
{"data": "some data one"},
{"data": "some data two"},
{"data": "some data three"},
],
)
conn.commit()
Session
:
Session = sessionmaker(engine)
with Session() as session:
session.add_all(
[
SomeClass(data="some data one"),
SomeClass(data="some data two"),
SomeClass(data="some data three"),
]
)
session.commit()
只初始化一次
sessionmaker
和 Engine
均提供了 Engine.begin()
方法,该方法将获取一个新对象来执行 SQL 语句(分别是 Session
和 Connection
),然后返回一个上下文管理器,用于维护该对象的开始/提交/回滚上下文。
引擎:
代码语言:javascript复制engine = create_engine("postgresql psycopg2://user:pass@host/dbname")
with engine.begin() as conn:
conn.execute(
some_table.insert(),
[
{"data": "some data one"},
{"data": "some data two"},
{"data": "some data three"},
],
)
# commits and closes automatically
会话:
代码语言:javascript复制Session = sessionmaker(engine)
with Session.begin() as session:
session.add_all(
[
SomeClass(data="some data one"),
SomeClass(data="some data two"),
SomeClass(data="some data three"),
]
)
# commits and closes automatically
嵌套事务
当使用 SAVEPOINT 通过 Session.begin_nested()
或 Connection.begin_nested()
方法时,必须使用返回的事务对象来提交或回滚 SAVEPOINT。调用 Session.commit()
或 Connection.commit()
方法将始终提交最外层的事务;这是 SQLAlchemy 2.0 特有的行为,与 1.x 系列相反。
引擎:
代码语言:javascript复制engine = create_engine("postgresql psycopg2://user:pass@host/dbname")
with engine.begin() as conn:
savepoint = conn.begin_nested()
conn.execute(
some_table.insert(),
[
{"data": "some data one"},
{"data": "some data two"},
{"data": "some data three"},
],
)
savepoint.commit() # or rollback
# commits automatically
会话:
代码语言:javascript复制Session = sessionmaker(engine)
with Session.begin() as session:
savepoint = session.begin_nested()
session.add_all(
[
SomeClass(data="some data one"),
SomeClass(data="some data two"),
SomeClass(data="some data three"),
]
)
savepoint.commit() # or rollback
# commits automatically
边提交边执行
Session
和Connection
均提供Connection.commit()
和Connection.rollback()
方法。使用 SQLAlchemy 2.0 风格的操作,这些方法在所有情况下都会影响到最外层的事务。对于Session
,假定Session.autobegin
保持其默认值为True
。
Engine
:
engine = create_engine("postgresql psycopg2://user:pass@host/dbname")
with engine.connect() as conn:
conn.execute(
some_table.insert(),
[
{"data": "some data one"},
{"data": "some data two"},
{"data": "some data three"},
],
)
conn.commit()
Session
:
Session = sessionmaker(engine)
with Session() as session:
session.add_all(
[
SomeClass(data="some data one"),
SomeClass(data="some data two"),
SomeClass(data="some data three"),
]
)
session.commit()
开始一次
sessionmaker
和Engine
均提供Engine.begin()
方法,该方法将获取一个新对象以执行 SQL 语句(分别是Session
和Connection
),然后返回一个上下文管理器,用于维护该对象的开始/提交/回滚上下文。
引擎:
代码语言:javascript复制engine = create_engine("postgresql psycopg2://user:pass@host/dbname")
with engine.begin() as conn:
conn.execute(
some_table.insert(),
[
{"data": "some data one"},
{"data": "some data two"},
{"data": "some data three"},
],
)
# commits and closes automatically
会话:
代码语言:javascript复制Session = sessionmaker(engine)
with Session.begin() as session:
session.add_all(
[
SomeClass(data="some data one"),
SomeClass(data="some data two"),
SomeClass(data="some data three"),
]
)
# commits and closes automatically
嵌套事务
使用通过Session.begin_nested()
或Connection.begin_nested()
方法创建的 SAVEPOINT 时,必须使用返回的事务对象提交或回滚 SAVEPOINT。调用Session.commit()
或Connection.commit()
方法将始终提交最外层的事务;这是 SQLAlchemy 2.0 特定行为,与 1.x 系列相反。
引擎:
代码语言:javascript复制engine = create_engine("postgresql psycopg2://user:pass@host/dbname")
with engine.begin() as conn:
savepoint = conn.begin_nested()
conn.execute(
some_table.insert(),
[
{"data": "some data one"},
{"data": "some data two"},
{"data": "some data three"},
],
)
savepoint.commit() # or rollback
# commits automatically
会话:
代码语言:javascript复制Session = sessionmaker(engine)
with Session.begin() as session:
savepoint = session.begin_nested()
session.add_all(
[
SomeClass(data="some data one"),
SomeClass(data="some data two"),
SomeClass(data="some data three"),
]
)
savepoint.commit() # or rollback
# commits automatically
显式开始
Session
具有“自动开始”行为,这意味着一旦开始执行操作,它就会确保存在一个 SessionTransaction
来跟踪正在进行的操作。当调用 Session.commit()
时,此事务将被完成。
通常情况下,特别是在框架集成中,控制“开始”操作发生的时间点是可取的。为此,Session
使用“自动开始”策略,以便可以直接调用 Session.begin()
方法来启动一个尚未开始事务的 Session
:
Session = sessionmaker(bind=engine)
session = Session()
session.begin()
try:
item1 = session.get(Item, 1)
item2 = session.get(Item, 2)
item1.foo = "bar"
item2.bar = "foo"
session.commit()
except:
session.rollback()
raise
上述模式通常使用上下文管理器更具惯用性:
代码语言:javascript复制Session = sessionmaker(bind=engine)
session = Session()
with session.begin():
item1 = session.get(Item, 1)
item2 = session.get(Item, 2)
item1.foo = "bar"
item2.bar = "foo"
Session.begin()
方法和会话的“自动开始”过程使用相同的步骤序列来开始事务。这包括当它发生时调用 SessionEvents.after_transaction_create()
事件;此钩子由框架使用,以便将其自己的事务处理集成到 ORM Session
的事务处理中。
启用两阶段提交
对于支持两阶段操作的后端(目前是 MySQL 和 PostgreSQL),可以指示会话使用两阶段提交语义。这将协调跨数据库的事务提交,以便在所有数据库中要么提交事务,要么回滚事务。您还可以为与 SQLAlchemy 未管理的事务交互的会话 Session.prepare()
。要使用两阶段事务,请在会话上设置标志 twophase=True
:
engine1 = create_engine("postgresql psycopg2://db1")
engine2 = create_engine("postgresql psycopg2://db2")
Session = sessionmaker(twophase=True)
# bind User operations to engine 1, Account operations to engine 2
Session.configure(binds={User: engine1, Account: engine2})
session = Session()
# .... work with accounts and users
# commit. session will issue a flush to all DBs, and a prepare step to all DBs,
# before committing both transactions
session.commit()
设置事务隔离级别 / DBAPI 自动提交
大多数 DBAPI 支持可配置的事务隔离级别的概念。传统上,这些级别是“READ UNCOMMITTED”、“READ COMMITTED”、“REPEATABLE READ”和“SERIALIZABLE”。这些通常在 DBAPI 连接开始新事务之前应用,注意大多数 DBAPI 在首次发出 SQL 语句时会隐式地开始此事务。
支持隔离级别的 DBAPI 通常也支持真正的“自动提交”概念,这意味着 DBAPI 连接本身将被放置到非事务自动提交模式中。这通常意味着数据库自动不再发出“BEGIN”,但也可能包括其他指令。使用此模式时,DBAPI 在任何情况下都不使用事务。SQLAlchemy 方法如.begin()
、.commit()
和.rollback()
会悄无声息地传递。
SQLAlchemy 的方言支持在每个Engine
或每个Connection
上设置可设置的隔离模式,使用的标志既可以在create_engine()
级别,也可以在Connection.execution_options()
级别。
当使用 ORM Session
时,它充当引擎和连接的门面,但不直接暴露事务隔离。因此,为了影响事务隔离级别,我们需要在适当的时候对Engine
或Connection
进行操作。
也请参阅
设置事务隔离级别,包括 DBAPI 自动提交 - 一定要查看 SQLAlchemy Connection
对象级别上隔离级别的工作方式。
设置会话/引擎范围的隔离级别
要全局设置一个具有特定隔离级别的Session
或sessionmaker
,第一种技术是可以在所有情况下构造一个具有特定隔离级别的Engine
,然后将其用作Session
和/或sessionmaker
的连接来源:
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
eng = create_engine(
"postgresql psycopg2://scott:tiger@localhost/test",
isolation_level="REPEATABLE READ",
)
Session = sessionmaker(eng)
另一个选项,如果同时存在两个具有不同隔离级别的引擎,可以使用Engine.execution_options()
方法,该方法将生成原始Engine
的浅拷贝,该浅拷贝与父引擎共享相同的连接池。当操作将被分为“事务”和“自动提交”操作时,通常更可取:
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
eng = create_engine("postgresql psycopg2://scott:tiger@localhost/test")
autocommit_engine = eng.execution_options(isolation_level="AUTOCOMMIT")
transactional_session = sessionmaker(eng)
autocommit_session = sessionmaker(autocommit_engine)
在上面的例子中,“eng
”和“autocommit_engine
”共享相同的方言和连接池。然而,当从autocommit_engine
获取连接时,将设置“AUTOCOMMIT”模式。然后,这两个sessionmaker
对象“transactional_session
”和“autocommit_session
”在与数据库连接一起工作时继承这些特性。
“autocommit_session
” 保持事务语义,包括Session.commit()
和Session.rollback()
仍然认为自己是“提交”和“回滚”对象,但事务将会静默不存在。因此,通常情况下,尽管不是严格要求,但一个具有 AUTOCOMMIT 隔离级别的 Session 应该以只读方式使用,即:
with autocommit_session() as session:
some_objects = session.execute(text("<statement>"))
some_other_objects = session.execute(text("<statement>"))
# closes connection
为单个 Session 设置隔离级别
当我们创建一个新的Session
时,可以直接传递bind
参数,覆盖预先存在的绑定,无论是直接使用构造函数还是调用由sessionmaker
产生的可调用对象。例如,我们可以从默认的sessionmaker
创建我们的Session
并传递一个设置为自动提交的引擎:
plain_engine = create_engine("postgresql psycopg2://scott:tiger@localhost/test")
autocommit_engine = plain_engine.execution_options(isolation_level="AUTOCOMMIT")
# will normally use plain_engine
Session = sessionmaker(plain_engine)
# make a specific Session that will use the "autocommit" engine
with Session(bind=autocommit_engine) as session:
# work with session
...
对于配置有多个binds
的Session
或sessionmaker
的情况,我们可以重新指定完整的binds
参数,或者如果我们只想替换特定的绑定,我们可以使用Session.bind_mapper()
或Session.bind_table()
方法:
with Session() as session:
session.bind_mapper(User, autocommit_engine)
为单个事务设置隔离级别
关于隔离级别的一个关键注意事项是,不能在已经启动事务的 Connection
上安全地修改设置。数据库无法更改正在进行的事务的隔离级别,并且一些 DBAPI 和 SQLAlchemy 方言在这个领域的行为不一致。
因此最好使用一个最初绑定到具有所需隔离级别的引擎的 Session
。但是,通过在事务开始时使用 Session.connection()
方法,可以影响每个连接的隔离级别:
from sqlalchemy.orm import Session
# assume session just constructed
sess = Session(bind=engine)
# call connection() with options before any other operations proceed.
# this will procure a new connection from the bound engine and begin a real
# database transaction.
sess.connection(execution_options={"isolation_level": "SERIALIZABLE"})
# ... work with session in SERIALIZABLE isolation level...
# commit transaction. the connection is released
# and reverted to its previous isolation level.
sess.commit()
# subsequent to commit() above, a new transaction may be begun if desired,
# which will proceed with the previous default isolation level unless
# it is set again.
在上面的示例中,我们首先使用构造函数或 sessionmaker
生成一个 Session
。然后,通过调用 Session.connection()
明确设置数据库级事务的开始,该方法提供了将传递给连接的执行选项,在开始数据库级事务之前。事务使用此选择的隔离级别进行。当事务完成时,连接上的隔离级别将重置为默认值,然后将连接返回到连接池。
Session.begin()
方法也可用于开始 Session
级事务;在此调用之后调用 Session.connection()
可以用于设置每个连接的事务隔离级别:
sess = Session(bind=engine)
with sess.begin():
# call connection() with options before any other operations proceed.
# this will procure a new connection from the bound engine and begin a
# real database transaction.
sess.connection(execution_options={"isolation_level": "SERIALIZABLE"})
# ... work with session in SERIALIZABLE isolation level...
# outside the block, the transaction has been committed. the connection is
# released and reverted to its previous isolation level.
为 Sessionmaker / Engine 设置隔离级别
要为 Session
或 sessionmaker
全局设置特定的隔离级别,第一种技术是可以在所有情况下构建一个针对特定隔离级别的 Engine
,然后将其用作 Session
和/或 sessionmaker
的连接来源:
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
eng = create_engine(
"postgresql psycopg2://scott:tiger@localhost/test",
isolation_level="REPEATABLE READ",
)
Session = sessionmaker(eng)
另一个选项,如果同时有两个具有不同隔离级别的引擎,则可以使用Engine.execution_options()
方法,它将生成原始Engine
的浅拷贝,与父引擎共享相同的连接池。当操作将分成“事务”和“自动提交”操作时,这通常是首选:
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
eng = create_engine("postgresql psycopg2://scott:tiger@localhost/test")
autocommit_engine = eng.execution_options(isolation_level="AUTOCOMMIT")
transactional_session = sessionmaker(eng)
autocommit_session = sessionmaker(autocommit_engine)
在上述示例中,“eng
”和“autocommit_engine
”都共享相同的方言和连接池。然而,当从autocommit_engine
获取连接时,将设置“AUTOCOMMIT”模式。然后,当这两个sessionmaker
对象“transactional_session
”和“autocommit_session
”与数据库连接一起工作时,它们继承了这些特征。
“autocommit_session
”仍然具有事务语义,包括当它们从autocommit_engine
获取时,Session.commit()
和Session.rollback()
仍然认为自己是“提交”和“回滚”对象,但事务将默默地不存在。因此,通常,虽然不是严格要求,但具有 AUTOCOMMIT 隔离的会话应该以只读方式使用,即:
with autocommit_session() as session:
some_objects = session.execute(text("<statement>"))
some_other_objects = session.execute(text("<statement>"))
# closes connection
为单个会话设置隔离
当我们创建一个新的Session
时,可以直接传递bind
参数,覆盖预先存在的绑定。例如,我们可以从默认的sessionmaker
创建我们的Session
,并传递设置为自动提交的引擎:
plain_engine = create_engine("postgresql psycopg2://scott:tiger@localhost/test")
autocommit_engine = plain_engine.execution_options(isolation_level="AUTOCOMMIT")
# will normally use plain_engine
Session = sessionmaker(plain_engine)
# make a specific Session that will use the "autocommit" engine
with Session(bind=autocommit_engine) as session:
# work with session
...
对于配置了多个binds
的Session
或sessionmaker
的情况,我们可以重新完全指定binds
参数,或者如果我们只想替换特定的绑定,我们可以使用Session.bind_mapper()
或Session.bind_table()
方法:
with Session() as session:
session.bind_mapper(User, autocommit_engine)
为单个事务设置隔离
关于隔离级别的一个关键警告是,在已经开始事务的 Connection
上无法安全地修改设置。数据库无法更改正在进行的事务的隔离级别,并且一些 DBAPI 和 SQLAlchemy 方言在这个领域的行为不一致。
因此,最好使用一个明确绑定到具有所需隔离级别的引擎的 Session
。但是,可以通过在事务开始时使用 Session.connection()
方法来影响每个连接的隔离级别:
from sqlalchemy.orm import Session
# assume session just constructed
sess = Session(bind=engine)
# call connection() with options before any other operations proceed.
# this will procure a new connection from the bound engine and begin a real
# database transaction.
sess.connection(execution_options={"isolation_level": "SERIALIZABLE"})
# ... work with session in SERIALIZABLE isolation level...
# commit transaction. the connection is released
# and reverted to its previous isolation level.
sess.commit()
# subsequent to commit() above, a new transaction may be begun if desired,
# which will proceed with the previous default isolation level unless
# it is set again.
在上面的例子中,我们首先使用构造函数或 sessionmaker
来生成一个 Session
。然后,我们通过调用 Session.connection()
显式设置数据库级事务的开始,该方法提供将在开始数据库级事务之前传递给连接的执行选项。事务会以此选定的隔离级别继续进行。当事务完成时,连接上的隔离级别将被重置为其默认值,然后连接将返回到连接池。
Session.begin()
方法也可用于开始 Session
级别的事务;在此调用之后调用 Session.connection()
可用于设置每个连接的事务隔离级别:
sess = Session(bind=engine)
with sess.begin():
# call connection() with options before any other operations proceed.
# this will procure a new connection from the bound engine and begin a
# real database transaction.
sess.connection(execution_options={"isolation_level": "SERIALIZABLE"})
# ... work with session in SERIALIZABLE isolation level...
# outside the block, the transaction has been committed. the connection is
# released and reverted to its previous isolation level.
使用事件跟踪事务状态
请参阅 事务事件 部分,了解会话事务状态更改的可用事件挂钩的概述。
将会话加入到外部事务(例如用于测试套件)
如果正在使用的Connection
已经处于事务状态(即已建立Transaction
),则可以通过将Session
绑定到该Connection
来使Session
参与该事务。通常的理由是一个测试套件允许 ORM 代码自由地与Session
一起工作,包括能够调用Session.commit()
,之后整个数据库交互都被回滚。
从版本 2.0 开始更改:在 2.0 中,“加入外部事务”配方再次得到了改进;不再需要事件处理程序来“重置”嵌套事务。
该配方通过在事务内部建立Connection
(可选地建立 SAVEPOINT),然后将其传递给Session
作为“bind”来实现;传递了Session.join_transaction_mode
参数,设置为"create_savepoint"
,这表示应创建新的 SAVEPOINT 以实现Session
的 BEGIN/COMMIT/ROLLBACK,这将使外部事务保持与传递时相同的状态。
当测试拆卸时,外部事务将被回滚,以便撤消测试期间的任何数据更改:
代码语言:javascript复制from sqlalchemy.orm import sessionmaker
from sqlalchemy import create_engine
from unittest import TestCase
# global application scope. create Session class, engine
Session = sessionmaker()
engine = create_engine("postgresql psycopg2://...")
class SomeTest(TestCase):
def setUp(self):
# connect to the database
self.connection = engine.connect()
# begin a non-ORM transaction
self.trans = self.connection.begin()
# bind an individual Session to the connection, selecting
# "create_savepoint" join_transaction_mode
self.session = Session(
bind=self.connection, join_transaction_mode="create_savepoint"
)
def test_something(self):
# use the session in tests.
self.session.add(Foo())
self.session.commit()
def test_something_with_rollbacks(self):
self.session.add(Bar())
self.session.flush()
self.session.rollback()
self.session.add(Foo())
self.session.commit()
def tearDown(self):
self.session.close()
# rollback - everything that happened with the
# Session above (including calls to commit())
# is rolled back.
self.trans.rollback()
# return connection to the Engine
self.connection.close()
上述配方是 SQLAlchemy 自身 CI 的一部分,以确保其按预期工作。 ession.connection()` 显式设置数据库级事务的开始,该方法提供将在开始数据库级事务之前传递给连接的执行选项。事务会以此选定的隔离级别继续进行。当事务完成时,连接上的隔离级别将被重置为其默认值,然后连接将返回到连接池。
Session.begin()
方法也可用于开始 Session
级别的事务;在此调用之后调用 Session.connection()
可用于设置每个连接的事务隔离级别:
sess = Session(bind=engine)
with sess.begin():
# call connection() with options before any other operations proceed.
# this will procure a new connection from the bound engine and begin a
# real database transaction.
sess.connection(execution_options={"isolation_level": "SERIALIZABLE"})
# ... work with session in SERIALIZABLE isolation level...
# outside the block, the transaction has been committed. the connection is
# released and reverted to its previous isolation level.
使用事件跟踪事务状态
请参阅 事务事件 部分,了解会话事务状态更改的可用事件挂钩的概述。
将会话加入到外部事务(例如用于测试套件)
如果正在使用的Connection
已经处于事务状态(即已建立Transaction
),则可以通过将Session
绑定到该Connection
来使Session
参与该事务。通常的理由是一个测试套件允许 ORM 代码自由地与Session
一起工作,包括能够调用Session.commit()
,之后整个数据库交互都被回滚。
从版本 2.0 开始更改:在 2.0 中,“加入外部事务”配方再次得到了改进;不再需要事件处理程序来“重置”嵌套事务。
该配方通过在事务内部建立Connection
(可选地建立 SAVEPOINT),然后将其传递给Session
作为“bind”来实现;传递了Session.join_transaction_mode
参数,设置为"create_savepoint"
,这表示应创建新的 SAVEPOINT 以实现Session
的 BEGIN/COMMIT/ROLLBACK,这将使外部事务保持与传递时相同的状态。
当测试拆卸时,外部事务将被回滚,以便撤消测试期间的任何数据更改:
代码语言:javascript复制from sqlalchemy.orm import sessionmaker
from sqlalchemy import create_engine
from unittest import TestCase
# global application scope. create Session class, engine
Session = sessionmaker()
engine = create_engine("postgresql psycopg2://...")
class SomeTest(TestCase):
def setUp(self):
# connect to the database
self.connection = engine.connect()
# begin a non-ORM transaction
self.trans = self.connection.begin()
# bind an individual Session to the connection, selecting
# "create_savepoint" join_transaction_mode
self.session = Session(
bind=self.connection, join_transaction_mode="create_savepoint"
)
def test_something(self):
# use the session in tests.
self.session.add(Foo())
self.session.commit()
def test_something_with_rollbacks(self):
self.session.add(Bar())
self.session.flush()
self.session.rollback()
self.session.add(Foo())
self.session.commit()
def tearDown(self):
self.session.close()
# rollback - everything that happened with the
# Session above (including calls to commit())
# is rolled back.
self.trans.rollback()
# return connection to the Engine
self.connection.close()
上述配方是 SQLAlchemy 自身 CI 的一部分,以确保其按预期工作。