级联
映射器支持可配置的概念 cascade 行为对 relationship()
构造。这是指相对于特定对象在“父”对象上执行的操作 Session
应传播到该关系引用的项(例如“子”对象),并受 relationship.cascade
选择权。
层叠的默认行为仅限于所谓的层叠 保存更新 和 合并 设置。级联的典型“可选”设置是添加 删除 和 删除孤儿 选项;这些设置适用于相关对象,这些对象仅在附加到其父对象时存在,否则将被删除。
层叠行为是使用 relationship.cascade
选择权 relationship()
::
class Order(Base): __tablename__ = 'order' items = relationship("Item", cascade="all, delete-orphan") customer = relationship("User", cascade="save-update")
要在backref上设置层叠,可以将同一标志与 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
CASCADE选项暗示 刷新期满 使用时可能不需要的级联设置 异步I/O(异步) 扩展,因为它将比通常在显式IO上下文中适当的更积极地使相关对象过期。请参阅以下地址的注释: 使用AsyncSession时防止隐式IO 了解更多背景信息。
可以为指定的可用值列表 relationship.cascade
参数在下面的小节中描述。
保存更新
save-update
Cascade表示当一个对象被放置到 Session
通过 Session.add()
,所有与此关联的对象 relationship()
也应该添加到同一个 Session
. 假设我们有一个物体 user1
有两个相关对象 address1
, address2
::
>>> user1 = User() >>> address1, address2 = Address(), Address() >>> user1.addresses = [address1, address2]
如果我们增加 user1
到A 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
A save-update
cascade在从集合中移除项或将对象与标量属性取消关联时,可能会表现出令人惊讶的行为。在某些情况下,孤立的对象仍可能被拉入前父对象的 Session
,这样刷新过程就可以适当地处理相关对象。这种情况通常只发生在一个对象被移除时 Session
并添加到另一个:
>>> user1 = sess1.query(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
默认情况下,cascade是打开的,通常被认为是理所当然的;它通过允许对 Session.add()
在其中注册对象的整个结构 Session
马上。虽然它可以被禁用,但通常不需要这样做。
其中一例 save-update
层叠有时也会妨碍双向关系的发生,例如backrefs,这意味着子对象与特定父对象的关联可能具有父对象与子对象的隐式关联的效果。 Session
;此模式以及如何使用 relationship.cascade_backrefs
标志,在本节中讨论 在backrefs上控制级联 .
删除
这个 delete
Cascade表示当“父”对象被标记为删除时,其相关的“子”对象也应标记为删除。例如,如果我们有关系 User.addresses
具有 delete
配置的层叠:
class User(Base): # ... addresses = relationship("Address", cascade="all, delete")
如果使用上面的映射,我们有一个 User
对象和两个相关 Address
物体::
>>> user1 = sess.query(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
关系确实如此 not 有 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
cascade包括两种情况,其中sqlAlchemy必须决定将外键列设置为空,而不是完全删除行。
默认情况下,该功能完全独立于配置的数据库 FOREIGN KEY
约束本身可能配置 CASCADE
行为。为了更有效地与此配置集成,在 在具有ORM关系的DELETE cascade中使用外键 应该使用。
参见
对多对多关系使用delete cascade
这个 cascade="all, delete"
option同样适用于多对多关系,即 relationship.secondary
指示关联表。当父对象被删除,因此与其相关对象取消关联时,工作单元流程通常会从关联表中删除行,但保留相关对象不变。当与 cascade="all, delete"
,附加 DELETE
语句将针对子行本身执行。
以下示例适用于 多对多 为了说明 cascade="all, delete"
设置在 one 协会方面:
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 = Column(Integer, primary_key=True) children = relationship( "Child", secondary=association_table, back_populates="parents", cascade="all, delete" ) class Child(Base): __tablename__ = 'right' id = Column(Integer, primary_key=True) parents = relationship( "Parent", secondary=association_table, back_populates="children", )
上面,当 Parent
对象被标记为删除,使用 Session.delete()
,刷新过程将一如既往地从 association
表,但根据级联规则,它还将删除所有相关的 Child
排。
警告
如果以上情况 cascade="all, delete"
设置配置在 both 关系,则级联操作将继续级联所有 Parent
和 Child
对象,加载每个 children
和 parents
遇到集合并删除所有已连接的内容。通常不希望双向配置“删除”级联。
参见
在具有ORM关系的DELETE cascade中使用外键
SQLAlchemy的“delete”级联的行为与 ON DELETE
数据库的特性 FOREIGN KEY
约束。SQLAlchemy允许配置这些模式级别 DDL 使用 ForeignKey
和 ForeignKeyConstraint
构造;将这些对象与 Table
在元数据中进行了描述 更新和删除时 .
为了使用 ON DELETE
外键级联与 relationship()
,首先需要注意的是 relationship.cascade
设置仍然必须配置为匹配所需的“删除”或“设置空”行为(使用 delete
级联或省略),这样无论ORM还是数据库级约束都将处理实际修改数据库中数据的任务,ORM仍然能够适当地跟踪可能受影响的本地呈现对象的状态。
然后,还可以在以下位置提供一个附加选项 relationship()
指示ORM应该尝试对相关行本身运行删除/更新操作的程度,以及它应该在多大程度上依赖数据库端外键约束级联来处理任务;这是 relationship.passive_deletes
参数,并且它接受选项 False
(默认值), True
和 "all"
。
最典型的例子是当父行被删除时要删除子行,并且 ON DELETE CASCADE
在相关 FOREIGN KEY
约束条件:
class Parent(Base): __tablename__ = 'parent' id = Column(Integer, primary_key=True) children = relationship( "Child", back_populates="parent", cascade="all, delete", passive_deletes=True ) class Child(Base): __tablename__ = 'child' id = Column(Integer, primary_key=True) parent_id = 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
旗帜是 not 开始吧relationship()
然后SELECT
卸载语句Child
物体会被发射出来。A
DELETE
语句随后针对my_parent
划船本身。数据库级别
ON DELETE CASCADE
设置可确保child
引用中受影响的行parent
也被删除。这个
Parent
引用的实例my_parent
,以及Child
与这个物体有关 加载 (即发生上述第2步),与Session
.
注解
要使用“ON DELETE CASCADE”,底层数据库引擎必须支持 FOREIGN KEY
约束,它们必须强制执行:
使用MySQL时,必须选择适当的存储引擎。见 创建包含存储引擎的表参数 有关详细信息。
使用sqlite时,必须显式启用外键支持。见 外键支持 有关详细信息。
Notes on Passive Deletes
重要的是要注意ORM和关系数据库的“级联”概念之间的差异以及它们如何集成:
数据库级别
ON DELETE
层叠在 many-to-one 关系的一方;也就是说,我们相对于FOREIGN KEY
约束是关系的“多”方面。在ORM级别, 这个方向是反的 . sqlAlchemy处理从“parent”端相对于“parent”删除“child”对象,这意味着delete
和delete-orphan
层叠配置在 one-to-many 一边。没有的数据库级外键
ON DELETE
设置通常用于 防止 不删除父行,因为它必然会留下未处理的相关行。如果在一对多关系中需要此行为,则SQLAlchemy的默认行为是将外键设置为NULL
可以通过以下两种方式之一捕获:最简单也是最常见的是将外键保持列设置为
NOT NULL
在数据库架构级别。SQLAlchemy尝试将列设置为空将失败,并出现简单的非空约束异常。另一种更特殊的方法是
relationship.passive_deletes
标记到字符串"all"
. 这会完全禁用SQLAlchemy将外键列设置为空的行为,并且会对父行发出删除操作,而不会对子行产生任何影响,即使子行存在于内存中。在数据库级别的外键触发器(无论是特殊的还是特殊的)的情况下,这可能是可取的。ON DELETE
如果删除父行,则需要在所有情况下激活设置或其他设置。
数据库级
ON DELETE
cascade通常比依赖SQLAlchemy的“cascade”删除特性要高效得多。数据库可以一次在多个关系之间连锁一系列级联操作;例如,如果删除a行,则表B中的所有相关行都可以被删除,所有与这些B行相关的C行以及on和on都在一个DELETE语句的作用域内。另一方面,为了完全支持级联删除操作,SQLAlchemy必须单独加载每个相关集合,以便将所有可能具有进一步相关集合的行作为目标。也就是说,SQLAlchemy不够成熟,不能在这个上下文中同时对所有相关行发出删除。sqlacalchemy没有 need 为了做到这一点,我们提供了与数据库本身的平滑集成
ON DELETE
功能,通过使用relationship.passive_deletes
选项与正确配置的外键约束结合使用。在这种行为下,SQLAlchemy只对本地已经存在于Session
;对于任何已卸载的集合,它都会将它们留给数据库进行处理,而不是为它们发出一个选择。断面 在具有ORM关系的DELETE cascade中使用外键 提供了此用法的示例。当数据库级别
ON DELETE
功能只在关系的“多”方面起作用,sqlacalchemy的“delete”cascade具有 有限的 能够在 颠倒 方向,也就是说,当删除“多”侧的引用时,可以在“多”侧配置为删除“一”侧的对象。但是,如果有其他对象引用“多”中的“一”面,这很容易导致约束冲突,因此通常只有当关系实际上是“一对一”时才有用。这个relationship.single_parent
应该使用标志为此案例建立一个in-python断言。
对多对多关系在DELETE上使用外键
如所述 对多对多关系使用delete cascade “删除”级联也适用于多对多关系。利用 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 = Column(Integer, primary_key=True) children = relationship( "Child", secondary=association_table, back_populates="parents", cascade="all, delete", ) class Child(Base): __tablename__ = 'right' id = Column(Integer, primary_key=True) parents = relationship( "Parent", secondary=association_table, back_populates="children", passive_deletes=True )
使用上述配置,删除 Parent
目标进行如下:
A
Parent
对象被标记为删除,使用Session.delete()
.当冲洗发生时,如果
Parent.children
集合未加载,ORM将首先发出SELECT语句以加载Child
对象对应于Parent.children
.然后它就会发射出来
DELETE
中的行的语句association
对应于父行。对于每一个
Child
对象受此立即删除影响,因为passive_deletes=True
不必为每一个单元发出的都是不需要尝试的工作语句Child.parents
集合中的相应行association
将被删除。DELETE
然后为每个语句发出语句Child
从中加载的对象Parent.children
.
删除孤儿
delete-orphan
层叠将行为添加到 delete
层叠,这样当子对象与父对象取消关联时,子对象将被标记为删除,而不仅仅是在父对象被标记为删除时。这是处理父集合“拥有”的相关对象时的一个常见功能,该对象具有非空的外键,因此从父集合中删除该项会导致删除该项。
delete-orphan
cascade意味着每个子对象一次只能有一个父对象 vast majority of cases is configured only on a one-to-many relationship. 对于将其设置为多对一或多对多关系的不太常见的情况,可以通过配置 relationship.single_parent
参数,它建立了Python端的验证,以确保对象一次只与一个父对象相关联,但是这极大地限制了“many”关系的功能,而且通常不是理想的。
参见
对于relationship<relationship>,delete orphan cascade通常只在一对多关系的“一”侧配置,而不是在多对一或多对多关系的“多”侧配置。 -涉及删除孤立级联的常见错误场景的背景。
合并
merge
层叠表示 Session.merge()
操作应该从父级传播,父级是 Session.merge()
调用引用的对象。默认情况下,此级联也处于启用状态。
刷新期满
refresh-expire
是一个不常见的选项,表示 Session.expire()
操作应该从父对象传播到被引用的对象。使用时 Session.refresh()
,引用的对象仅过期,但实际未刷新。
删去
expunge
cascade表示当父对象从 Session
使用 Session.expunge()
,该操作应向下传播到引用的对象。
在backrefs上控制级联
注解
本节适用于在SQLAlchemy 2.0中删除的行为。通过设置 Session.future
在给定的 Session
,将实现2.0行为,这本质上是 relationship.cascade_backrefs
标志被忽略。参见章节 cascade_backrefs行为在2.0中不推荐删除 做笔记。
在 1.x style ORM用法 保存更新 默认情况下,层叠发生在从backrefs发出的属性更改事件上。这可能是一个更容易通过演示描述的令人困惑的语句;这意味着,给定这样的映射:
mapper_registry.map_imperatively(Order, order_table, properties={ 'items' : relationship(Item, backref='order') })
如果一个 Order
已在会话中,并分配给 order
AN属性 Item
,backref附加了 Item
到 items
收集 Order
,导致 save-update
级联发生:
>>> o1 = Order() >>> session.add(o1) >>> o1 in session True >>> i1 = Item() >>> i1.order = o1 >>> i1 in o1.items True >>> i1 in session True
可以使用禁用此行为 relationship.cascade_backrefs
旗帜:
mapper_registry.map_imperatively(Order, order_table, properties={ 'items' : relationship(Item, backref='order', cascade_backrefs=False) })
所以上面的任务 i1.order = o1
将追加 i1
到 items
收藏 o1
,但不会添加 i1
参加会议。当然,你可以, Session.add()
i1
稍后再参加会议。此选项可能有助于在对象构造完成之前将其从会话中保留出来,但仍需要将其与目标会话中已持久存在的对象关联。
当关系由 relationship.backref
参数打开 relationship()
,即 sqlalchemy.orm.cascade_backrefs
参数可以设置为 False
在BACKREF端使用 backref()
函数而不是字符串。例如,可以将上述关系声明为::
mapper_registry.map_imperatively(Order, order_table, properties={ 'items' : relationship( Item, backref=backref('order', cascade_backrefs=False), cascade_backrefs=False ) })
这将设置 cascade_backrefs=False
两种关系的行为。
删除引用的对象和标量关系的注释
通常,ORM在刷新过程中从不修改集合或标量关系的内容。这意味着,如果你的班级 relationship()
如果引用了一个对象集合,或者引用了单个对象(如多对一),则在刷新过程发生时不会修改此属性的内容。相反,我们期望 Session
将最终过期,或者通过 Session.commit()
或通过明确使用 Session.expire()
. 此时,任何与之关联的引用对象或集合 Session
将被清除,并在下次访问时重新加载。
在这种行为中出现的一种常见的混淆包括使用 Session.delete()
方法。什么时候? Session.delete()
对对象和 Session
刷新后,该行将从数据库中删除。通过外键引用目标行的行,假定使用 relationship()
在两个映射对象类型之间,还将看到它们的外键属性更新为空,或者如果设置了删除层叠,则相关行也将被删除。但是,即使与已删除对象相关的行本身也可能被修改, no changes occur to relationship-bound collections or object references on the objects 参与冲洗本身范围内的操作。这意味着,如果对象是相关集合的成员,则在该集合过期之前,它仍然存在于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
CASCADE实现了这一点,如下例所示:
class User(Base): __tablename__ = 'user' # ... addresses = relationship( "Address", cascade="all, delete-orphan") # ... del user.addresses[1] session.flush()
在上面的位置,移除 Address
对象从 User.addresses
收藏 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
参见
级联 有关级联的详细信息。