You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SQLAlchemy级联删除后对象仍可查询?会话管理疑问

关于SQLAlchemy会话管理的理解误区分析

问题场景

模型定义

class User(BaseModel):
    cars = relationship(
        "Car",
        back_populates="user",
        passive_deletes="all",
    )

class Car(BaseModel):
     user_id: Mapped[Optional[uuid.UUID]] = mapped_column(
        ForeignKey("user_id", ondelete="CASCADE"),
        index=True,
    )

测试代码

@staticmethod
async def test_car_is_cascade_deleted(
    db, fixture_user, fixture_car
):
    user = await fixture_user() # 用于创建User对象并执行FLUSH的工厂函数
    car = await fixture_car(
        user=user
    )

    await db.refresh(car)
    await db.delete(user)
    await db.commit()
    assert await db.get(User, user.id) is None
    assert await db.get(car, car.id) is None # 若不调用await db.refresh(car)则断言失败

现象

  • 必须调用await db.refresh(car),否则await db.get(car, car.id)会返回实际对象,导致断言失败
  • SQL日志显示数据库已执行Car和User的删除语句:
DELETE FROM car WHERE car.user_id = %(user_id)s::UUID
2024-07-18 17:48:46,521 INFO sqlalchemy.engine.Engine [generated in 0.00007s] [{'user_id': 'b652d7c5-6328-4725-b276-914264c0064a'}, {'user_id': 'b652d7c5-6328-4725-b276-914264c0064a'}]
2024-07-18 17:48:46,523 INFO sqlalchemy.engine.Engine DELETE FROM user WHERE user.id = %(id)s::UUID {'id': 'b652d7c5-6328-4725-b276-914264c0064a'}

核心理解误区

1. 混淆会话缓存与实际数据库状态

SQLAlchemy的AsyncSession会维护一个身份映射缓存,所有通过会话加载、创建的对象都会存在这里。执行db.delete(user)并commit后,数据库里的Car确实被CASCADE删除了,但会话缓存里的car对象不会自动标记为已删除——除非会话主动感知到这个变化。

  • 你之前创建的car对象还在缓存中,所以db.get(Car, car.id)会优先返回缓存里的对象,而非去数据库查询
  • 只有调用db.refresh(car)时,会话才会去数据库重新获取对象状态,发现它已不存在后更新缓存,此时db.get才会返回None

2. 误解passive_deletes="all"的作用

passive_deletes="all"只是告诉SQLAlchemy:父对象删除时,不要主动生成子对象的DELETE语句(因为数据库层面已经通过ondelete="CASCADE"处理了)。但它完全不负责同步会话缓存里的子对象状态,和缓存更新没有任何关系。

3. 错误认为commit会自动清理缓存

commit仅会将会话中的修改同步到数据库,不会主动清空或更新缓存里的对象。缓存里的对象依然保留着commit前的状态,除非手动刷新(refresh)、过期(expire)或者关闭会话。

正确处理方式

如果不想依赖refresh,可以用以下方法:

  • 执行await db.expire_all()在commit后过期所有缓存对象,后续db.get会重新查询数据库
  • 关闭并重新创建会话,新会话的缓存是空的
  • 测试时直接执行显式查询,比如await db.execute(select(Car).where(Car.id == car.id)),显式查询默认会绕过缓存(除非设置特殊选项)

内容的提问来源于stack exchange,提问作者Christian

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 20:42:05