级联


映射器支持可配置的级联行为的概念 relationship() 结构。这是指在相对于特定 Session 的 “父” 对象上执行的作应该如何传播到该关系引用的项目(例如,“子”对象),并受 relationship.cascade 选项的影响。


级联的默认行为仅限于所谓的 save-updatemerge 设置的级联。cascade 的典型 “alternative” 设置是添加 deletedelete-orphan 选项;这些设置适用于相关对象,这些对象仅在附加到其父对象时存在,否则将被删除。


级联行为是使用 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 选项意味着 刷新过期 cascade 设置,这在使用 异步 I/O (asyncio) 扩展,因为它会比在显式 IO 上下文中通常更积极地使相关对象过期。有关更多背景信息,请参阅使用 AsyncSession 时防止隐式 IO 中的注释。


以下小节介绍了可为 relationship.cascade 参数指定的可用值列表。


保存-更新


save-update 级联表示当对象被放入 Session 中,所有通过此 relationship() 与之关联的对象也应该添加到同一个 Session 中。假设我们有一个对象 user1 和两个相关的对象 address1address2

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


如果我们将 user1 添加到 Session 中,它还会将 address1address2 隐式地:

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


save-update cascade 还会影响 Session 中已存在的对象的属性作。如果我们向 user1.addresses 集合添加第三个对象 address3,它将成为该 Session 状态的一部分:

>>> address3 = Address()
>>> user1.addresses.append(address3)
>>> address3 in sess
True


保存-更新级联在从集合中删除项或将对象与标量属性取消关联时,可能会表现出令人惊讶的行为。在某些情况下,孤立的对象可能仍会被拉取到前父对象的 Session 中;这样 flush 进程就可以适当地处理该相关对象。通常,仅当从一个 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 cascade 的行为


save-update 级联在双向关系的上下文中单发生,即使用 relationship.back_populatesrelationship.backref 时 parameters 创建两个单独的 relationship() 对象。


未与 Session 关联的对象,当分配给 父对象上与 Session 将自动添加到同一 会话。但是,反向相同的作不会产生此效果;一个未与 Session 关联的对象,与 Session 关联的子对象位于该对象上 分配的 API API 的 API API 中,不会导致该父对象自动添加到 会话。此行为的总体主题称为“级联 backrefs”,它代表了从 SQLAlchemy 2.0 开始标准化的行为变化。


为了说明这一点,给定一个 Order 对象的映射,这些对象通过关系与一系列 Item 对象双向相关 Order.itemsItem.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.itemscollection 的 collection 中,该 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.itemsItem.order 的双向性质意味着附加到 Order.items 也会分配给 Item.order。同时,save-update 级联允许将 Item 对象添加到父 Order 已经关联的同一 Session 中。


但是,如果上述作是反向执行的,Item.order 是赋值而不是直接附加到 Order.item 中,级联作将 不会自动进行,即使对象分配 Order.itemsItem.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 中,旧行为被弃用,cascade_backrefs 选项在 SQLAlchemy 2.0 中被删除。 理由是,用户通常不觉得分配 分配给对象上的属性,如上所示为 i1.order = o1 会改变该对象 i1 的持久性状态,使其现在在 Session 中处于待处理状态,并且经常会出现后续问题,即自动刷新会过早刷新对象并导致错误,在这些情况下,给定对象仍在构造中并且未处于准备刷新状态。在单向和双向行为之间进行选择的选项也被删除,因为此选项创建了两种略有不同的工作方式,增加了 ORM 的整体学习曲线以及文档和用户支持的负担。


另请参阅


cascade_backrefs 行为在 2.0 中被弃用并删除 - “级联反向引用”行为更改的背景


删除


删除级联指示当 “parent” 对象被标记为删除时,其相关的 “child” 对象也应标记为删除。例如,如果我们有一个关系 User.addresses 配置了 DELETE CASCADE:

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 标记为删除,则在 flush作继续后, address1address2 也将被删除:

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


或者,如果我们的 User.addresses 关系没有 delete cascade 时,SQLAlchemy 的默认行为是改为取消关联 address1address2 的调用,方法是将其外键引用设置为 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 cascade 通常与 delete-orphan cascade 结合使用,如果 “child” 对象与父对象取消关联,它将为相关行发出 DELETE。删除删除孤立级联的组合涵盖了 SQLAlchemy 必须在将外键列设置为 NULL 与完全删除行之间做出决定的两种情况。


默认情况下,该功能完全独立于数据库配置 FOREIGN KEY 约束,这些约束本身可以配置 CASCADE 行为。为了更有效地与此配置集成,应使用将外键 ON DELETE 级联与 ORM 关系一起使用中描述的其他指令。


警告


请注意,ORM 的 “delete” 和 “delete-orphan” 行为适用 仅适用于使用 Session.delete() 方法在工作单元进程中标记要删除的单个 ORM 实例。它不适用于 “批量” 删除,这将使用 delete() 结构发出,如 具有自定义 WHERE 条件的 ORM UPDATE 和 DELETE。 看 启用 ORM 的更新和删除的重要说明和注意事项,以获取更多背景信息。


使用多对多关系的 delete cascade


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 对象标记为删除时,刷新过程将照常从关联表中删除关联的行,但根据级联规则,它也将删除所有相关的 Child 行。


警告


如果在 两个 relationships 中,则级联作将继续级联所有 ParentChild 对象,加载每个 childrenparents 集合并删除已连接的所有内容。 是的 通常不希望配置 “delete” 级联 双向。


在 ORM 关系中使用外键 ON DELETE 级联


SQLAlchemy 的 “delete” 级联的行为与 ON DELETE 功能。 SQLAlchemy 允许使用 ForeignKeyForeignKeyConstraint 配置这些模式级 DDL 行为 构建;将这些对象与 Table 结合使用 元数据在 ON UPDATE 和 ON DELETE 中进行了描述。


为了将 ON DELETE 外键级联与 relationship()中,首先需要注意的是, relationship.cascade 设置仍必须配置为匹配所需的 “delete” 或 “set null” 行为(使用 Delete Cascade 或省略),以便无论 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")


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


  1. 应用程序调用 session.delete(my_parent),其中 my_parentParent 的实例。


  2. Session 下次刷新对数据库的更改时,my_parent.children当前加载的所有项目 collection 被 ORM 删除,这意味着每条记录都会发出一个 DELETE 语句。


  3. 如果 my_parent.children 集合已卸载,则无需 DELETE 语句。 如果 relationship.passive_deletes 标志,则 SELECT 已卸载的 Child 对象的语句。


  4. 然后,将为 my_parent 行本身发出 DELETE 语句。


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


  6. my_parent引用的 Parent 实例,以及与此对象相关且 loaded 的 (即上述步骤 2 已执行),与 会话


注意


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


在多对多关系中使用外键 ON DELETE


对多对多关系使用删除级联中所述,“删除”级联也适用于多对多关系。使用 ON DELETE CASCADE FOREIGN KEYS 与多对多结合使用时,在 association table 上配置 FOREIGN KEY 指令。这些指令可以处理从 association table 中自动删除的任务,但不能容纳相关对象本身的自动删除。


在这种情况下,relationship.passive_deletes 指令可以在删除作期间为我们节省一些额外的 SELECT 语句,但 ORM 仍将继续加载一些集合,以便找到受影响的子对象并正确处理它们。


注意


对此的假设优化可能包括单个 DELETE 对 association 表的所有父关联行的语句 一次,然后使用 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 对象的过程如下:


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


  2. 当刷新发生时,如果 Parent.children 集合没有加载,ORM 将首先发出一个 SELECT 语句来加载 ChildParent.children 对应的对象。


  3. 然后,它将为关联的行发出 DELETE 语句 对应于该父行。


  4. 对于每个受立即删除影响的 Child 对象,因为 passive_deletes=True 时,工作单元将不需要尝试为每个 Child.parents 集合发出 SELECT 语句,因为假定关联的相应行将被删除。


  5. 然后,为从 Parent.children 加载的每个 Child 对象发出 DELETE 语句。


delete-orphan(删除孤立项)


delete-orphan 级联将行为添加到 Delete 级联中,这样,当子对象与父级分离时,子对象将被标记为删除,而不仅仅是在父对象被标记为删除时。当使用 NOT NULL 外键处理由其父级“拥有”的相关对象时,这是一个常见功能,因此从父集合中删除该项目会导致其被删除。


delete-orphan 级联意味着每个子对象一次只能有一个父级,并且在绝大多数情况下都配置了 仅在一对多关系上。对于在多对一或多对多关系上设置它的不太常见的情况,可以通过配置 relationship.single_parent 参数强制“多”端一次只允许一个对象,该参数建立 Python 端验证,确保对象一次只与一个父级关联,但是这极大地限制了“多”关系的功能,通常不是我们想要的。


合并


merge cascade 表示 Session.merge() 作应从作为使用者的父级传播 的 Session.merge() 调用的 Session.merge() 调用的 Interface。默认情况下,此级联也是打开的。


刷新过期


refresh-expire 是一个不常见的选项,表示 Session.expire()作应该从父级向下传播到引用的对象。当使用 Session.refresh() 时,引用的对象只会过期,但实际上不会刷新。


删除


expunge cascade 表示当使用 Session.expunge()Session 中删除父对象时,该作应该向下传播到引用的对象。


删除 - 删除从集合和标量关系中引用的对象的说明¶


ORM 通常从不修改集合或标量的内容 关系。 这意味着,如果您的类具有 relationship() 引用对象集合,或引用单个对象(例如多对一),则当刷新过程发生时,此属性的内容将不会被修改。相反,预计 Session 最终会过期,要么通过 Session.commit() 或通过显式使用 Session.expire() 来获取。 此时,与该对象关联的任何引用对象或集合 会话将被清除,并在下次访问时重新加载自身。


关于此行为出现的常见混淆涉及使用 Session.delete() 方法。当对对象调用 Session.delete() 并刷新 Session 时,该行将从数据库中删除。通过外键引用目标行的行,假设它们是使用两个映射对象类型之间的 relationship() 进行跟踪的, 还将看到它们的外键属性 UPDATED 为 null,或者如果 delete cascade 设置后,相关行也将被删除。然而,即使 尽管与已删除对象相关的行本身也可能被修改, 上 的关系绑定集合或对象引用不会发生任何更改 flush 本身范围内的作所涉及的对象。这意味着,如果该对象是相关集合的成员,则它仍将存在于 Python 端,直到该集合过期。同样,如果对象是通过另一个对象的多对一或一对一引用的,则该引用将一直存在于该对象上,直到该对象也过期。


下面,我们说明了在 Address 对象被标记为删除后,它仍然存在于与父 User 关联的集合中,即使在 flush 之后也是如此:

>>> 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 对象时 collection,则 delete-orphan 级联具有标记 Address 的效果 对象进行删除,其方式与将其传递给 Session.delete() 的方式相同。


删除-孤立级联还可以应用于多对一或一对一关系,以便在对象与其父级取消关联时,也会自动将其标记为删除。使用 delete-orphan 多对一或一对一上的 cascade 需要一个额外的标志 relationship.single_parent 调用一个断言,即此相关对象不会同时与任何其他父对象共享:

class User(Base):
    # ...

    preference = relationship(
        "Preference", cascade="all, delete-orphan", single_parent=True
    )


在上面,如果从 User 中删除了一个假设的 Preference 对象,它将在 flush 时被删除:

some_user.preference = None
session.flush()  # will delete the Preference object


另请参阅


Cascades 了解有关 cascades 的详细信息。