内容提要
PostgreSQL的pg_dump在恢复数据时,若存在触发器,可能因转储文件将search_path设为空字符串,导致触发器函数中未加schema限定的表名无法解析,进而使恢复失败并回滚整个COPY操作,表保持为空。解决方案包括使用--disable-triggers、延迟约束或设置session_replication_role,但这些方法各有权限或副作用限制。
延伸解读
空 search_path 是根因
pg_dump 数据转储在文件开头设置 search_path 为空字符串,导致触发器函数中未加 schema 限定的表名无法解析。即使目标表存在,也会报“relation does not exist”。此问题与目标表是否有数据无关,仅由转储自身的设置触发。
触发器错误回滚整个 COPY
当触发器在 COPY 过程中出错时,整个 COPY 语句会回滚,导致该表保持为空,而其他表正常加载。这会造成部分加载的假象,且序列 setval 仍会执行,导致序列值超前于实际数据。
三种缓解措施及其代价
--disable-triggers 需要表所有者权限,否则会失败;SET CONSTRAINTS ALL DEFERRED 需要单事务,但触发器错误会回滚整个事务;session_replication_role = replica 需要超级用户或显式授权。每种方法都有权限或副作用限制,需根据场景选择。
检查恢复是否成功
psql 默认在错误后继续执行,且退出码为 0,容易掩盖失败。建议使用 -v ON_ERROR_STOP=1 让 psql 在首个错误时停止并返回非零退出码。pg_restore 则默认返回 1 并报告忽略的错误数,更适合自动化检查。
Q&A
为什么 pg_dump 数据恢复会报错 relation "book_audit" does not exist?
因为 pg_dump 在转储文件开头设置了 search_path 为空字符串,导致触发器函数中未加 schema 限定的表名无法解析,从而报错。
pg_dump 数据恢复时,触发器错误会导致什么后果?
触发器错误会回滚整个 COPY 操作,导致该表保持为空,而其他表正常加载。
如何解决 pg_dump 恢复时触发器导致的错误?
有三种方法:使用 --disable-triggers 选项、使用延迟约束(SET CONSTRAINTS ALL DEFERRED)、设置 session_replication_role = replica。但各有权限或副作用限制。
pg_dump --disable-triggers 选项有什么限制?
需要表的所有权或超级用户权限,否则会报错 'must be owner of table'。此外,它会禁用所有触发器,包括外键约束检查,可能导致不一致的数据被加载。
pg_restore 和 psql 在错误报告上有什么区别?
pg_restore 会以非零退出码(1)报告错误,并显示 'errors ignored on restore: N';而 psql 默认退出码为 0,除非设置 -v ON_ERROR_STOP=1,否则不会报告错误。
恢复失败后,序列(sequence)与表数据不同步怎么办?
可以使用 setval 将序列重置为表的最大 id,例如 SELECT setval('public.books_id_seq', (SELECT max(id) FROM public.books));
为什么 pg_dump 恢复时会出现重复键错误?
当目标数据库已有数据时,恢复的 COPY 操作会尝试插入与现有主键冲突的行,导致重复键错误。
如何避免 pg_dump 恢复时触发器函数中的表名解析问题?
在触发器函数中,对表名使用 schema 限定,例如 INSERT INTO public.book_audit(...),这样可以避免 search_path 为空时无法解析。