SQLAlchemy 2.0 文档
级联¶
映射器支持在 relationship()
构造上配置 级联 行为的概念。 这指的是相对于特定 Session
在“父”对象上执行的操作应如何传播到该关系引用的项(例如“子”对象),并受 relationship.cascade
选项的影响。
级联的默认行为仅限于所谓的 save-update 和 merge 设置的级联。 级联的典型“替代”设置是添加 delete 和 delete-orphan 选项; 这些设置适用于仅在其连接到父对象时才存在的关联对象,否则将被删除。
级联行为使用 relationship.cascade
选项在 relationship()
上配置
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) 扩展时,这可能不是理想的,因为它会比在显式 IO 上下文中通常更积极地使关联对象过期。 有关更多背景信息,请参阅 使用 AsyncSession 时防止隐式 IO 中的注释。
可以在 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
中的对象的属性操作。 如果我们向 user1.addresses
集合添加第三个对象 address3
,它将成为该 Session
状态的一部分
>>> address3 = Address()
>>> user1.addresses.append(address3)
>>> address3 in sess
True
当从集合中删除项目或取消对象与标量属性的关联时,save-update
级联可能会表现出令人惊讶的行为。 在某些情况下,孤立的对象可能仍会被拉入前父对象的 Session
; 这是为了使刷新过程可以适当地处理该关联对象。 这种情况通常仅在对象从一个 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 级联的行为¶
在双向关系的上下文中,save-update
级联是 单向 的,即当使用 relationship.back_populates
或 relationship.backref
参数创建两个相互引用的单独的 relationship()
对象时。
未与 Session
关联的对象,当分配给与 Session
关联的父对象上的属性或集合时,将自动添加到同一 Session
。 但是,反向操作不会产生这种效果; 未与 Session
关联的对象,在其上分配了与 Session
关联的子对象时,不会导致自动将该父对象添加到 Session
。 此行为的总体主题被称为“级联反向引用”,代表了 SQLAlchemy 2.0 标准化的行为更改。
为了说明,给定 Order
对象的映射,这些对象通过关系 Order.items
和 Item.order
双向关联到一系列 Item
对象
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 中,save-update 级联将在所有情况下双向发生。 然后,使用一个名为 cascade_backrefs
的选项使其成为可选的。 最后,在 SQLAlchemy 1.4 中,旧的行为已被弃用,并且 cascade_backrefs
选项在 SQLAlchemy 2.0 中被删除。 理由是用户通常不认为分配给对象上的属性(如上例所示的 i1.order = o1
的分配)会改变该对象 i1
的持久性状态,使其现在在 Session
中处于挂起状态,并且在给定对象仍在构建且未处于准备好刷新的状态的情况下,自动刷新经常会过早刷新对象并导致错误。 选择单向和双向行为的选项也被删除,因为此选项创建了两种略有不同的工作方式,增加了 ORM 的总体学习曲线以及文档和用户支持负担。
另请参阅
cascade_backrefs 行为已弃用,将在 2.0 中删除 - 关于“级联反向引用”行为更改的背景
delete¶
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 的默认行为是将 address1
和 address2
从 user1
中取消关联,方法是将其外键引用设置为 NULL
。 使用以下映射
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
行为。 为了更有效地与此配置集成,应使用 将外键 ON DELETE 级联与 ORM 关系一起使用 中描述的其他指令。
警告
请注意,ORM 的“delete”和“delete-orphan”行为仅适用于使用 Session.delete()
方法在 工作单元 进程中标记要删除的单个 ORM 实例。 它 不 适用于“批量”删除,批量删除将使用 delete()
构造发出,如 使用自定义 WHERE 条件的 ORM UPDATE 和 DELETE 中所示。 有关更多背景信息,请参阅 ORM 启用更新和删除的重要注意事项和警告。
在多对多关系中使用 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 关系一起使用¶
SQLAlchemy 的“delete”级联的行为与数据库 FOREIGN KEY
约束的 ON DELETE
功能重叠。 SQLAlchemy 允许使用 ForeignKey
和 ForeignKeyConstraint
构造配置这些模式级 DDL 行为; Table
元数据结合使用这些对象的描述见 ON UPDATE 和 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.passive_deletes
标志 未 在此relationship()
上设置,则将为未加载的Child
对象发出SELECT
语句。然后,为
my_parent
行本身发出DELETE
语句。数据库级
ON DELETE CASCADE
设置确保child
中引用parent
中受影响行的所有行也被删除。由
my_parent
引用的Parent
实例,以及与此对象相关且 已加载 的所有Child
实例(即执行了上面的步骤 2),都将从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
语句。
delete-orphan¶
delete-orphan
级联为 delete
级联添加了行为,以便在子对象从父对象解除关联时(而不仅仅是在父对象被标记为删除时)将其标记为删除。当处理由其父对象“拥有”的相关对象(具有 NOT NULL 外键)时,这是一个常见的功能,因此从父对象集合中删除该项会导致其删除。
delete-orphan
级联意味着每个子对象一次只能有一个父对象,并且在绝大多数情况下仅在 一对多关系上配置。 对于在多对一或多对多关系上设置它的不太常见的情况,“多”方可以被强制为一次只允许一个对象,通过配置 relationship.single_parent
参数,该参数建立 Python 端的验证,以确保对象一次仅与一个父对象关联,但这大大限制了“多”关系的功用,通常不是期望的结果。
另请参阅
对于关系 <relationship>,delete-orphan 级联通常仅在一对多关系的“一”方配置,而不是在多对一或多对多关系的“多”方配置。 - 关于涉及 delete-orphan 级联的常见错误场景的背景信息。
merge¶
merge
级联指示 Session.merge()
操作应从作为 Session.merge()
调用的主体的父对象传播到引用的对象。此级联默认也是开启的。
refresh-expire¶
refresh-expire
是一个不常见的选项,指示 Session.expire()
操作应从父对象传播到引用的对象。当使用 Session.refresh()
时,引用的对象仅过期,但实际上并未刷新。
expunge¶
expunge
级联指示当使用 Session.expunge()
从 Session
中删除父对象时,该操作应向下传播到引用的对象。
关于删除的注意事项 - 删除从集合和标量关系引用的对象¶
通常,ORM 在刷新过程中永远不会修改集合或标量关系的内容。这意味着,如果您的类具有 relationship()
,该关系引用对象集合,或者引用单个对象(例如多对一),则在刷新过程发生时,此属性的内容不会被修改。相反,期望 Session
最终会过期,无论是通过 Session.commit()
的 expire-on-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
级联的效果是将 Address
对象标记为删除,方式与将其传递给 Session.delete()
相同。
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
另请参阅
级联 有关级联的详细信息。
flambé! 龙和 The Alchemist 图像设计由 Rotem Yaari 创建并慷慨捐赠。
使用 Sphinx 7.2.6 创建。文档最后生成时间:Tue 11 Mar 2025 02:40:17 PM EDT