DEBUG.WIKI
雨云;给项目一台云服务器
Evoxt;最高 6.0GHz CPU;高频云服务器 · 免费每周备份;查看配置 →
PanstarCloud:部署应用,接入 AI,云服务器与 AI 网关

SQLAlchemy DELETE 第二次丢了 RETURNING,检查编译缓存版本

SQLAlchemy 2.1.5 的 Core Delete.return_defaults 选项未完整进入缓存键,可能复用不带 RETURNING 的 SQL。正式 2.1.6 已修正,核对执行顺序和真实返回字段。

同一条 Core DELETE 在全新 engine 上能得到 returned_defaults,同一 engine 先执行过普通 DELETE 后,它却变成 None。如果实际执行日志里连 RETURNING 都消失了,应检查语句编译缓存,而不是先认定数据库没有返回被删除的行。

SQLAlchemy 2.1.5 中,Delete.return_defaults() 的若干选项没有完整进入缓存键。普通 DELETE、带默认返回列的 DELETE,以及使用 supplemental_cols 的形式可能共用一个键,后执行的语句复用了先前的 SQL 形状。交换执行顺序,还可能让原本普通的 DELETE 意外带上 RETURNING。

正式 2.1.6 已把相关状态纳入缓存键

2.1.6 的正式修正包含 _return_defaults、默认返回列及补充返回列等状态。2.0 分支把对应修正列入 2.0.56,但当前没有正式发行日期,也没有可安装的正式 2.0.56。继续使用 2.0 的项目需保留有界规避或等待正式发行,不应只为这条回归直接跨到另一个系列。

复核时保留同一个 engine 与同一批 Table 对象,分别按“普通形式在前”和“返回列形式在前”执行。每个用例若都重新创建 engine,缓存从未被复用,就容易让回归消失。仅对语句单独调用 compile() 看见正确 SQL,也不能证明实际缓存执行正确。

可先用不连接数据库的编译核对,确认两种声明本来就要求不同 SQL:

from sqlalchemy import Column, Integer, MetaData, Table, delete
from sqlalchemy.dialects import postgresql

t = Table("example", MetaData(),
          Column("id", Integer), Column("value", Integer))
plain = delete(t).where(t.c.id == 7)
with_values = plain.return_defaults(t.c.value)
print(plain.compile(dialect=postgresql.dialect()))
print(with_values.compile(dialect=postgresql.dialect()))

这段只编译,不执行删除。真正的缓存回归应在隔离数据上检查执行 SQL 与返回值,不要把生产表代入验证用例。

临时规避也要限定范围

暂时保留旧依赖时,可以评估在确有需要的执行连接上关闭 compiled cache,代价是这段执行失去编译缓存收益。不要清空数据库缓存、改隔离级别或重新启动数据库;问题发生在客户端编译语句的复用。

return_defaults() 与显式 .returning() 的返回接口也不同,不能只为避开问题机械替换后继续读取旧属性。若改接口,应同时审校调用方如何消费返回行以及所需列是否受方言支持。

升级或局部调整后核对返回列、无匹配行、不同补充列和两种执行顺序。若错误返回值已经影响了业务后续动作,应单独核查那些动作;SQL 缓存修复不会撤销已经提交的删除。