SQLAlchemy 2.x如何手动取消行过期?嵌套事务回滚后字段为空
问题:嵌套事务回滚后SQLAlchemy对象过期导致属性丢失的解决方法
前置条件
- PostgreSQL 14
- SQLAlchemy 2.0.19
- Python 3.10
- Asyncpg 0.28.0
场景代码
async with sessionmaker() as session, session.begin(): person: Person = await session.get(Person, (person_id, )) print(person.age) try: async with session.begin_nested(): person.age = -1000 await session.flush() except Exception as ex: pass # 忽略异常 print(person.age)
操作流程
- 获取指定ID的Person对象;
- 在子事务(检查点)中故意将age设为非法值;
- 子事务因检查约束违规失败;
- 捕获异常后,事务回滚至检查点启动前状态并继续执行。
问题现象
- 预期:修改仅在错误检查点内执行,Person对象未被修改,可正常读取
person.age; - 实际:Person对象变为expired(过期)状态,数据库与Python对象状态不一致。
尝试手动取消过期时,执行以下代码会导致对象所有字段变为None:
state = attributes.instance_state(person) state.expired = False state.expired_attributes.clear()
注:禁止使用refresh()(已通过应用级锁确保数据无其他访问,refresh()会破坏设计目的)
解决方案
问题核心是:直接取消过期状态时,SQLAlchemy会认为对象属性未被加载,因此返回None。正确做法是先保存属性快照,回滚后恢复原始值,再重置对象状态。
修改后的代码示例:
from sqlalchemy import attributes async with sessionmaker() as session, session.begin(): person: Person = await session.get(Person, (person_id, )) print(person.age) # 保存未修改的属性快照 state = attributes.instance_state(person) original_attrs = {} for key, attr in state.attrs.items(): if not attr.history.has_changes(): original_attrs[key] = getattr(person, key) try: async with session.begin_nested(): person.age = -1000 await session.flush() except Exception as ex: # 恢复原始属性值 for key, value in original_attrs.items(): setattr(person, key, value) # 重置对象状态:取消过期+清除变更记录 state.expired = False state.expired_attributes.clear() for attr in state.attrs.values(): attr.history.clear() print(person.age)
原理说明
- 嵌套事务前保存未修改的属性值,避免回滚后ORM丢失原始状态;
- 恢复属性后清空变更历史,让ORM认为对象从未被修改;
- 此时取消过期状态,ORM会使用已恢复的属性值,不会返回
None。
另一种简化方案(适合无并发修改场景):
try: async with session.begin_nested(): person.age = -1000 await session.flush() except Exception as ex: session.expunge(person) session.add(person)
通过先移除再重新添加对象,重置ORM对对象状态的追踪,同样能避免属性丢失。
内容的提问来源于stack exchange,提问作者winwin
相关产品推荐
相关产品推荐

