SQLAlchemy 关闭编译缓存后,from_statement 丢失 params 值
SQLAlchemy 2.1 的 ORM FromStatement 在无缓存键编译路径中可能丢失 params。正式 2.1.6 已修正;临时将值传给 Session.execute,保留参数绑定和原查询权限条件。
ORM 查询用 .from_statement(text(...)).params(n="a") 绑定了值,启用 query_cache_size=0 后却报 A value is required for bind parameter 'n'。默认缓存下正常,关闭缓存才失败时,应检查 SQLAlchemy 2.1 的 FromStatement 编译路径。
受影响实现会在缓存键中携带这些值;没有编译缓存键时,FromStatement 没有把自己的 .params() 正确注册给编译器。于是 stmt.compile().params 可能显示 {'n': None},最终执行也缺值。这不是数据库里不存在名为 n 的列。
正式 2.1.6 已修正无缓存键路径
SQLAlchemy 2.1.6 的正式发行说明包含这项修正。确认实际环境加载相应正式版本后,保留原来关闭缓存的配置重新核对,不能通过重新打开缓存就宣称无缓存分支已经恢复。
暂时不升级时,可以在执行入口传入参数,而不是依赖外层 FromStatement 的 .params():
from sqlalchemy import select, text
stmt = select(Task).from_statement(
text("select * from task where name = :n")
)
items = session.execute(stmt, {"n": "a"}).scalars().all()
这里 Task 是已有映射,session 是应用当前事务中的 Session,表名和条件沿用该映射。实际查询的租户、权限及其他筛选必须保留。也可以在内部 text() 上按其公开接口绑定值,但应只选择一个清楚的参数归属,避免多处覆盖。
不要把参数插进 SQL 字符串
为了让打印出来的 SQL“带上值”,改成格式化字符串或拼接引号,会失去值绑定的保护,也无法解释这条编译回归。literal_binds 主要用于特定编译展示,不适合作为外部输入的执行修复。
该问题限定 ORM 的 FromStatement 与无缓存键路径。普通 select()、text() 或另一种 .params() 入口出现缺值时,仍需检查真实传参与占位符是否一致;不能只要看到“需要绑定参数”就断定是这个版本问题。
复核应比较默认缓存、关闭缓存和直接编译三种入口,分别使用两个不同参数值,确认查询确实按本次值返回结果。还要保留查不到记录的情况,避免一个固定样本恰好命中,掩盖值被固定或丢失的问题。


