SQLAlchemy await freeze 后读取缓存,报 coroutine 不可迭代
旧 AsyncResult.freeze 可能把未等待的 fetchall 协程存进 FrozenResult。正式 2.1.6 已改正取行路径;核对缓存真正可重读,并保留结果消费和对象状态边界。
frozen = await result.freeze() 没有报错,把它存入缓存以后,再调用 frozen().all() 却报 TypeError: 'coroutine' object is not iterable,并出现 AsyncResult.fetchall was never awaited 警告。需要检查 freeze 真正保存了什么,不能只把它当成少写了一个 await。
SQLAlchemy 2.1.5 的 AsyncResult 冻结路径会让 FrozenResult 按同步方式调用结果的 fetchall()。异步版本返回的是协程,于是缓存里得到协程而不是已读取的行。返回单个 ORM 实体的分支还可能更早报 _raw_row_iterator 属性不存在。
采用正式修补后核对两次读取
正式 2.1.6 的 FrozenResult 改为在桥接上下文内获取实际行,并补上异步结果所需的原始行迭代入口。2.0 分支计划在 2.0.56 带上对应修补,但当前没有正式发行日期,不能据此安装一个尚未发行的版本。核对时保留原来的 stream 入口,冻结前不要先把结果消费掉:
result = await session.stream(stmt)
try:
frozen = await result.freeze()
finally:
await result.close()
first_read = frozen().all()
second_read = frozen().all()
session 和 stmt 来自应用已有的受控查询。这两个读取结果应具有相同的行内容和结构。新生成的是可重复消费的 Result 包装器,不能据此认定每次都会创建完全独立的业务对象或深复制每个字段。
不应在缓存读取失败后再给 frozen() 随手加一个 await。它按接口返回同步 Result;问题是旧冻结过程保存的内容错误,不是所有后续调用都应该异步化。
已经存入的坏条目需要单独处理
升级依赖不会把缓存里已有的协程变成行。根据应用的缓存标识失效受影响条目,再从原查询重新生成;不要为这个问题清空不相关业务数据。若缓存跨进程或持久化,还需要原本就批准的序列化格式与对象身份策略,FrozenResult 不是任意对象的通用持久化协议。
临时使用 await result.all() 保存普通行列表也是接口变化:列表不是 callable FrozenResult,ORM 对象和连接生命周期也需要单独判断。不能把它原样塞进期待 frozen() 的旧缓存入口。
冻结会完整读取剩余结果,内存消耗与结果规模相关;它不保留原流式读取的恒定内存特点。复核至少覆盖 Core 行、ORM 单实体、空结果以及应用实际的 unique 和列筛选,确认重复读取真正可用,而不只是 freeze 那一行没有异常。


