DEBUG.WIKI
V.PS:让项目靠近用户,亚太、欧洲、北美 KVM VPS
IPPeak:从目标地区访问网页,住宅代理、SEO 监测、广告验证
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国

AsyncSession 回滚后访问已加载属性,为什么还会报 MissingGreenlet

回滚后按明确的异步边界重新加载属性,核对整笔事务与保存点范围,保留失败事务处理。

同一个 AsyncSession 里,对象的标量属性之前已经读过,甚至对象也已提交;处理一次 IntegrityError 并调用 await session.rollback() 后,再访问这个属性,却报 MissingGreenlet: greenlet_spawn has not been called。如果代码已经设置 expire_on_commit=False,还要检查回滚是否让对象状态过期。

这里讨论 SQLAlchemy 异步 ORM 的事务回滚与属性重新加载。以下处理按 SQLAlchemy 2.0 的行为说明;MissingGreenlet 也可能来自尚未加载的关系或其他隐式查询,不能只凭异常名字确定发生过回滚。

之前读过的值,回滚后可能需要重新查询

Session 回滚一个活动事务时,会让仍保留在 Session 中的 ORM 对象状态过期。这条规则不受 expire_on_commit 控制,也不限于刚才写入失败的那一个对象。对象还在 Python 内存里,不代表属性仍可直接读取。

因此,“先查询对象、执行另一笔写入、捕获错误后回滚、读取旧对象的 name”也可能失败。读取过期属性需要向数据库发查询,而普通的 obj.name 没有供异步 I/O 使用的 await 边界。异常出现在属性访问处,看上去与前面的事务错误隔了一段代码。

先沿调用顺序找真正的 rollback,检查对象是否属于这个 Session,以及错误后是否继续使用它。不要仅检查提交时是否过期;expire_on_commit=False 处理的是 commit 后的行为。

flush 失败后,Session 还可能处于需要显式回滚才能继续使用的状态。跳过 rollback 来保住内存中的值,会留下未正确结束的事务,随后出现另一类错误。关于这个事务边界,可对照flush 失败后 Session 需要回滚的处理。

需要当前数据库状态,就显式等待加载

原事务已正确结束,且对象仍是这个 Session 中可刷新的一条持久化记录时,可以在获准的新读取事务中明确刷新需要的属性。下面是现有模型中的调用片段,item 应替换为实际对象,name 必须是实际映射属性:

await session.refresh(item, ["name"])
name = item.name

refresh 可能启动事务,并用数据库值覆盖对象状态。应先确认行仍存在、当前业务允许重新读取,以及没有需要保留的未提交改动。对象已被移出 Session、已删除或不再属于当前会话时,应重新按业务身份查询,不能用刷新掩盖生命周期问题。

按 SQLAlchemy 2.0 的异步设计,也可以用受支持的显式加载方式组织读取。AsyncAttrs.awaitable_attrs 从 2.0.13 起提供可等待的属性访问;它要求模型已采用相应 mixin。不要直接给一个未配置的普通对象加 await,再认为任意属性访问都能异步查询。

若业务只需要错误发生前的某个标量值,可以在回滚之前保存独立值,并明确它只是当时的值。涉及价格、权限或其他必须读取当前状态的字段,不能把旧值缓存当成刷新替代。

单条记录允许失败时,考虑 SAVEPOINT 的范围

批量处理里,一条记录违反约束不一定需要撤销整批工作。数据库和当前事务管理方式支持 SAVEPOINT 时,可以把允许局部失败的写入放到 begin_nested() 的上下文内,让这个上下文回滚自己的保存点。

外层事务由现有业务流程拥有时,不要在异常处理里又调用 Session.rollback();这个调用会回滚 Session 的外层事务,失去原本的局部隔离。应使用 nested 上下文或它返回的事务句柄。

SAVEPOINT 仍有两个需要核对的地方。进入 begin_nested() 会先 flush 当前待处理状态,即使设置了 autoflush=False;如果错误来自这次预先 flush,它并没有被即将创建的保存点保护。保存点回滚也会使保存点内被修改的状态过期,后续异步读取仍须按加载规则处理。

所以不能给所有代码简单套一层 nested。先确定事务是谁开启的、哪些对象已经待写入、哪一种错误允许跳过,以及跳过以后整笔业务是否仍成立。

修改前保留原事务与异常处理代码,在隔离数据中分别核对提交、整笔回滚和保存点回滚后的读取。验收要看到需要的查询发生在明确的 await 内,失败事务已经结束,允许保留的外层操作按预期提交。代码回退不会恢复已经提交或已回滚的业务结果。