DEBUG.WIKI
京东云;让 AI 进入真实业务;零售 · 物流 · 客服 · 硬件;查看方案 →
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国
Evoxt;最高 6.0GHz CPU;高频云服务器 · 免费每周备份;查看配置 →

SQLAlchemy 空 do_orm_execute 监听,也让 yield_per 关系加载失败

SQLAlchemy 的空 do_orm_execute 监听也可能让内部加载选项泄漏,导致 yield_per 与关系加载失败,2.0.50包含修补。

查询原本使用 yield_per 和 select-in 关系加载正常,增加一个什么也不做的 do_orm_execute 监听后,却报:

Can't use the ORM yield_per feature in conjunction with unique()

监听函数没有改语句,也没有主动调用 unique(),并不意味着事件路径与无事件路径完全一致。受影响实现会在事件执行前后进行两轮 ORM 预处理,第一轮产生的内部执行选项泄漏到第二轮及关系加载路径,触发了不该出现的组合。

对照空监听与原查询

在独立测试 Session 中,可以保留同一份关系映射和数据,只增加一个空监听:

from sqlalchemy import event, select
from sqlalchemy.orm import selectinload

stmt = (
    select(Message)
    .options(selectinload(Message.corrections))
    .execution_options(yield_per=5)
)

def noop(state):
    pass

event.listen(session, "do_orm_execute", noop)
try:
    for row in session.scalars(stmt):
        print(row.id, len(row.corrections))
finally:
    event.remove(session, "do_orm_execute", noop)

Message.corrections 对应应用已有的 select-in 关系,测试数据应包含已知关联行数。这里移除的只是临时空监听,不能顺带关闭 Session 上原有的租户、权限或其他业务事件。

SQLAlchemy 2.0.50 包含这项修补。修补后的第二轮预处理以原始选项为基础,只保留事件中显式更新的内容,避免带入第一轮的内部加载状态。更新到包含修补且兼容项目的版本后,再比较有、无空监听的结果。

不必一律关闭流式读取或关系加载

如果那个监听本来就是无用的调试入口,可以移除它;确有业务用途时,应保留事件行为并修补运行库。直接取消所有关系加载、关闭所有事件或切换语句缓存,会改变应用行为,也没有解释这个选项传播问题。

yield_per 与手工 Result.unique() 的限制仍然存在。集合 joined eager loading、subquery eager loading 也有各自的流式读取限制;select-in 加载还取决于驱动能否支持所需的游标行为。不能把这项修补理解成所有加载策略都能任意组合。

验收要覆盖主查询每行的值、关联集合内容及完整迭代结束,而不是只执行 session.scalars() 就认为成功。错误可能在迭代或关系加载时才显现。对于空监听,修补前后的对照应保持查询和数据不变;对于真实监听,还应确认其显式设置的执行选项仍然生效。