asyncpg 在线改表后报 InvalidCachedStatementError:旧缓存遇到新结构
在线修改 PostgreSQL 结构后,旧连接的查询计划或类型缓存可能失效。区分事务内外处理、直接 asyncpg 与 SQLAlchemy 的缓存参数,并安排迁移和连接生命周期。
服务保持运行,另一个部署任务修改了 PostgreSQL 表结构,随后 asyncpg 查询报 InvalidCachedStatementError: cached statement plan is invalid due to a database schema or configuration change。若连接在修改前已经使用过该查询,先检查旧连接中的预备语句缓存与新结构是否冲突,并确认失败时有没有正在执行的事务。
这里讨论在线 DDL 后的连接缓存失效,不是普通 SQL 拼写或数据库网络中断。还应核对同期是否修改了 search_path、枚举或复合类型等配置;并非所有 ALTER TABLE 都必然触发同一种错误。
事务内的失败,不能只重发那一条查询
asyncpg 缓存预备语句以复用查询计划。表或查询结果类型变化后,PostgreSQL 可能拒绝按旧计划继续执行。当前驱动处理这类错误时,会清理该连接及同一池相关的语句缓存;没有正在执行的事务时,可以重新准备并尝试一次。
如果错误发生在事务内,PostgreSQL 已使事务进入失败状态,驱动会把异常向上抛。清理缓存不会把已失败的事务恢复为可继续提交的状态。应让事务管理器按原设计结束、回滚,再判断是否允许重做整个业务事务,不能在同一失败事务里循环重发查询。
保留第一条错误、应用进程及连接池信息、DDL 时间和事务范围。线上有多个应用进程时,一次请求恢复正常,不代表其他进程里的旧连接都已经刷新。重试涉及外部消息、支付或其他副作用时,数据库回滚也不会撤销那些操作,应按业务幂等规则处理。
区分直接 asyncpg 与 SQLAlchemy 的缓存参数
直接使用 asyncpg 的连接配置中,statement_cache_size=0 用于关闭普通语句缓存。它可以作为迁移期间经过评估的兼容措施,但不能据此承诺没有任何类型缓存或显式预备语句问题。也不能把某次迁移里的性能反馈当成所有业务关闭缓存都没有成本。
SQLAlchemy 的 asyncpg 方言另有一层预备语句对象缓存。以下仅展示 SQLAlchemy 1.4 起支持的参数位置,连接地址要替换为项目已有的受控配置,并保留必要的 TLS 与认证设置:
from sqlalchemy.ext.asyncio import create_async_engine
engine = create_async_engine(
"postgresql+asyncpg://app_user:password@db.example.com/app_db"
"?prepared_statement_cache_size=0"
)
示例中的密码只是占位值,不应写入公开代码或日志。实际项目若使用独立连接参数或密钥管理,沿用原方式,不因这一项设置改成硬编码凭据。
prepared_statement_cache_size 与直接 asyncpg 的 statement_cache_size 不是同一个参数。SQLAlchemy 方言仍会使用 asyncpg.prepare,设置零只关闭这层缓存,不等于取消所有预备语句或 PostgreSQL 类型 OID 缓存。尤其修改 ENUM 等类型后,仍可能出现 cache lookup failed for type 相关错误。
SQLAlchemy 可以在本进程、本 engine 执行 DDL 时失效相关缓存;另一个迁移进程的变更无法被它提前可靠感知。发生错误后,它可以清理本地缓存,但不能阻止首次异常,也不能在已经失效的事务里替应用安全重试。
迁移流程要覆盖仍在使用旧连接的进程
能够安排维护窗口时,优先协调迁移与应用请求:保存部署版本、数据库结构和恢复方案,结束或排空受影响的业务事务,再按项目流程执行 DDL、刷新相关应用连接并恢复流量。不要随意中断尚未完成的写事务,更不要只重启其中一个进程就认为整个应用池已经更新。
具体连接刷新方式取决于直接 asyncpg、SQLAlchemy 和进程管理结构。已借出的连接与还在执行的请求,需要先按各自生命周期处理;创建新 pool 或 engine,不会自动证明旧任务已经停止使用原连接。
必须持续提供服务时,迁移要兼容旧、新应用版本,并明确缓存失效异常的处理策略。自动重试只能在原事务已结束、操作允许重做、重试次数受限的条件下实施。由异常处理吞掉错误,再返回业务成功,会掩盖没有提交的修改。
reload_schema_state 有范围,不能挽救失败事务
直接 asyncpg 提供 reload_schema_state,用来通知连接重新加载缓存的数据库结构信息,例如复合类型布局变化。它也会清理相关语句缓存,但作用范围不等于通知所有独立进程。
调用时要按驱动版本、连接来源及迁移顺序设计。已经因数据库错误进入失败状态的事务,仍需结束;在异常后调用这个方法,再继续使用原事务,不是完整修复。SQLAlchemy 的连接包装与它自己的缓存也要另行考虑,不直接操作包装器内部对象来跳过生命周期。
验收包括所有进程及业务事务结果
在项目获准环境由原来的业务入口重新验证,确认查询按新结构工作,不再从旧连接触发同一错误,并检查多个应用进程和后续连接复用。写入场景还应确认整笔事务的结果和外部副作用符合设计,不能只看最后一条查询成功。
调整缓存参数后观察实际查询行为与资源使用,再决定是否保留,不编造固定性能提升或损失。失败需要回退时,恢复兼容的应用与结构迁移版本,并重新协调连接生命周期;只还原缓存参数,不会撤销已经完成的 DDL 或恢复旧数据。


