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
相关产品推荐
相关产品推荐

