SqlAlchemy 2.0 中文文档(二十三)(1)https://developer.aliyun.com/article/1560514
删除
删除
级联表示当“父”对象标记为删除时,其相关的“子”对象也应标记为删除。例如,如果我们有一个配置了删除
级联的关系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
另请参阅
级联以获取级联的详细信息。
事务和连接管理
事务管理
在 1.4 版本中更改:会话事务管理已经修订为更清晰、更易于使用。特别是,它现在具有“自动开始”操作,这意味着可以控制事务开始的时间点,而不使用传统的“自动提交”模式。
Session
一次跟踪单个“虚拟”事务的状态,使用一个称为SessionTransaction
的对象。然后,此对象利用绑定到Session
对象的基础Engine
或引擎,根据需要使用Connection
对象开始真实的连接级事务。
此“虚拟”事务在需要时会自动创建,或者可以使用Session.begin()
方法启动。尽可能地支持 Python 上下文管理器的使用,既在创建Session
对象的层面,也在维护SessionTransaction
的范围方面。
假设我们从Session
开始:
from sqlalchemy.orm import Session session = Session(engine)
我们现在可以使用上下文管理器在一个确定的事务中运行操作:
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)
SqlAlchemy 2.0 中文文档(二十三)(3)https://developer.aliyun.com/article/1560520