在 PostgreSQL 的日常运维中,pg_dump 逻辑备份偶尔会报出 schema with OID xxx does not exist 这类错误。这类问题看似离奇,其实往往和事务隔离机制下的“孤儿对象”残留有关。云栈社区里也有不少 DBA 讨论过类似场景,今天就把这个坑完整梳理一遍。
适用范围:PostgreSQL 19 以前的版本
问题概述
Session 1:在事务中删除 myschema,未提交
create database mydb;
\c mydb
create schema myschema;
begin;
drop schema myschema cascade;
BEGIN
│
├── DROP SCHEMA myschema CASCADE
│
│ 当前事务看到:
│
│ myschema → 已删除
│
▼
未 COMMIT
Session 2:可以创建 myschema.f1 成功
CREATE FUNCTION myschema.f1()
returns void as $$
begin NULL; end;
$$ language plpgsql;
Session 1 事务提交后,pg_proc 系统表将残留 myschema.f1 的元数据记录。后续对该库做逻辑备份时,pg_dump 就会抛出:
error:schema with OID xxx does not exist
问题原因
这个问题的关键在于:DROP SCHEMA … CASCADE 在 Session 1 中虽然已经执行,但事务还没有提交。
Session 2 创建函数时,获取到的系统快照是一个“事务隔离后的数据库对象视图”。因为 Session 1 的 DROP SCHEMA 尚未提交,Session 2 仍然可以看到 myschema 的旧版本。
也就是说:Session 1 认为自己已经把 myschema 删掉了,但这个删除只对 Session 1 自己可见;Session 2 仍然认为 myschema 存在,所以照样可以在里面创建函数。
当 Session 1 提交事务后,myschema 的元数据信息被正式清除,但 pg_proc 系统表中残留的 f1 函数记录却没有对应的 pg_namespace 条目。pg_dump 在遍历函数对象时,按照 pg_proc 去反查命名空间,结果就撞上了 schema with OID xxx does not exist 的错误。
说到底,这是一个典型的并发 DDL 与事务可见性交互导致的元数据不一致问题。
解决方案
该问题在最新的 v19 已经修复。同时 v19 还提供了对 schema 的打包功能,可以对 schema 初始化依赖的对象进行原子操作,参考语法如下:
CREATE SCHEMA myschema
CREATE TABLE t1(id int)
CREATE VIEW v1 AS SELECT 1
CREATE FUNCTION f1() RETURNS int
LANGUAGE sql
AS $$ SELECT 1 $$;
对于还在使用 19 之前版本的环境,规避思路也比较直接:避免在一个未提交事务中执行 DROP SCHEMA … CASCADE 的同时,另一个会话还对该 schema 做对象创建。生产上的 DDL 变更最好串行执行,或者先将相关连接清理干净再动手。
参考文档:
https://www.postgresql.org/docs/19/release.html