Release: 1.4.25 | Release Date: September 22, 2021

SQLAlchemy 1.4 Documentation

级联

映射器支持可配置的概念 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.cascadesave-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 有两个相关对象 address1address2 ::

>>> user1 = User()
>>> address1, address2 = Address(), Address()
>>> user1.addresses = [address1, address2]

如果我们增加 user1 到A Session ,它还将添加 address1address2 隐含地:

>>> sess = Session()
>>> sess.add(user1)
>>> address1 in sess
True

save-update 层叠还影响已存在于 Session . 如果我们添加第三个对象, address3user1.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 对于删除,在执行刷新操作之后, address1address2 也将被删除:

>>> sess.delete(user1)
>>> sess.commit()
DELETE FROM address WHERE address.id = ? ((1,), (2,)) DELETE FROM user WHERE user.id = ? (1,) COMMIT

或者,如果我们的 User.addresses 关系确实如此 notdelete 层叠,sqlAlchemy的默认行为是取消关联 address1address2user1 通过将其外键引用设置为 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

删除 一对多关系上的级联通常与 删除孤儿 层叠,如果“子”对象从父对象中取消关联,将为相关行发出删除。结合 deletedelete-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 关系,则级联操作将继续级联所有 ParentChild 对象,加载每个 childrenparents 遇到集合并删除所有已连接的内容。通常不希望双向配置“删除”级联。

在具有ORM关系的DELETE cascade中使用外键

SQLAlchemy的“delete”级联的行为与 ON DELETE 数据库的特性 FOREIGN KEY 约束。SQLAlchemy允许配置这些模式级别 DDL 使用 ForeignKeyForeignKeyConstraint 构造;将这些对象与 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")

删除父行时,上述配置的行为如下:

  1. 应用程序调用 session.delete(my_parent) 在哪里 my_parent 是的实例 Parent .

  2. Session 接下来刷新对数据库的更改,所有 当前已加载 中的项目 my_parent.children 集合被ORM删除,这意味着 DELETE 语句将为每个记录发出。

  3. 如果 my_parent.children 收藏是 卸载 ,那就不行了 DELETE 发出语句。如果 relationship.passive_deletes 旗帜是 not 开始吧 relationship() 然后 SELECT 卸载语句 Child 物体会被发射出来。

  4. A DELETE 语句随后针对 my_parent 划船本身。

  5. 数据库级别 ON DELETE CASCADE 设置可确保 child 引用中受影响的行 parent 也被删除。

  6. 这个 Parent 引用的实例 my_parent ,以及 Child 与这个物体有关 加载 (即发生上述第2步),与 Session .

注解

要使用“ON DELETE CASCADE”,底层数据库引擎必须支持 FOREIGN KEY 约束,它们必须强制执行:

Notes on Passive Deletes

重要的是要注意ORM和关系数据库的“级联”概念之间的差异以及它们如何集成:

  • 数据库级别 ON DELETE 层叠在 many-to-one 关系的一方;也就是说,我们相对于 FOREIGN KEY 约束是关系的“多”方面。在ORM级别, 这个方向是反的 . sqlAlchemy处理从“parent”端相对于“parent”删除“child”对象,这意味着 deletedelete-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 目标进行如下:

  1. A Parent 对象被标记为删除,使用 Session.delete() .

  2. 当冲洗发生时,如果 Parent.children 集合未加载,ORM将首先发出SELECT语句以加载 Child 对象对应于 Parent.children .

  3. 然后它就会发射出来 DELETE 中的行的语句 association 对应于父行。

  4. 对于每一个 Child 对象受此立即删除影响,因为 passive_deletes=True 不必为每一个单元发出的都是不需要尝试的工作语句 Child.parents 集合中的相应行 association 将被删除。

  5. 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”关系的功能,而且通常不是理想的。

合并

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附加了 Itemitems 收集 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 将追加 i1items 收藏 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

参见

级联 有关级联的详细信息。

Previous: 国家管理 Next: 事务和连接管理