pg_savior

阻止不带条件的全表更新以避免意外事故

概览

扩展包名版本分类许可证语言
pg_savior0.1.0ADMINApache-2.0C
ID扩展名BinLibLoadCreateTrustReloc模式
5810pg_savior-
相关扩展pg_upless safeupdate pg_drop_events pg_cheat_funcs table_log pg_snakeoil pg_auditor temporal_tables

-tuplestore_donestoring , breaks on pg18 @ el

版本

类型仓库版本PG 大版本包名依赖
EXTPIGSTY0.1.01817161514pg_savior-
RPMPIGSTY0.1.01817161514pg_savior_$v-
DEBPIGSTY0.1.01817161514postgresql-$v-pg-savior-
OS / PGPG18PG17PG16PG15PG14
el8.x86_64
el8.aarch64
el9.x86_64
el9.aarch64
el10.x86_64
el10.aarch64
d12.x86_64
d12.aarch64
d13.x86_64
PIGSTY 0.1.0
PIGSTY 0.1.0
PIGSTY 0.1.0
PIGSTY 0.1.0
PIGSTY 0.1.0
d13.aarch64
PIGSTY 0.1.0
PIGSTY 0.1.0
PIGSTY 0.1.0
PIGSTY 0.1.0
PIGSTY 0.1.0
u22.x86_64
PIGSTY 0.1.0
PIGSTY 0.1.0
PIGSTY 0.1.0
PIGSTY 0.1.0
PIGSTY 0.1.0
u22.aarch64
PIGSTY 0.1.0
PIGSTY 0.1.0
PIGSTY 0.1.0
PIGSTY 0.1.0
PIGSTY 0.1.0
u24.x86_64
PIGSTY 0.1.0
PIGSTY 0.1.0
PIGSTY 0.1.0
PIGSTY 0.1.0
PIGSTY 0.1.0
u24.aarch64
PIGSTY 0.1.0
PIGSTY 0.1.0
PIGSTY 0.1.0
PIGSTY 0.1.0
PIGSTY 0.1.0
u26.x86_64
u26.aarch64

构建

您可以使用 pig build 命令构建 pg_savior 扩展的 RPM / DEB 包:

pig build pkg pg_savior         # 构建 RPM / DEB 包

安装

您可以直接安装 pg_savior 扩展包的预置二进制包,首先确保 PGDGPIGSTY 仓库已经添加并启用:

pig repo add pgsql -u          # 添加仓库并更新缓存

使用 pig 或者是 apt/yum/dnf 安装扩展:

pig install pg_savior;          # 当前活跃 PG 版本安装
pig ext install -y pg_savior -v 18  # PG 18
pig ext install -y pg_savior -v 17  # PG 17
pig ext install -y pg_savior -v 16  # PG 16
pig ext install -y pg_savior -v 15  # PG 15
pig ext install -y pg_savior -v 14  # PG 14
dnf install -y pg_savior_18       # PG 18
dnf install -y pg_savior_17       # PG 17
dnf install -y pg_savior_16       # PG 16
dnf install -y pg_savior_15       # PG 15
dnf install -y pg_savior_14       # PG 14
apt install -y postgresql-18-pg-savior   # PG 18
apt install -y postgresql-17-pg-savior   # PG 17
apt install -y postgresql-16-pg-savior   # PG 16
apt install -y postgresql-15-pg-savior   # PG 15
apt install -y postgresql-14-pg-savior   # PG 14

创建扩展

CREATE EXTENSION pg_savior;

用法

来源:README, release 0.1.0, PGXN 0.1.0, SQL file, C source, pg_savior.control

pg_savior 是一个 PostgreSQL 安全扩展,用于在执行前阻止特定高风险 DML 和 DDL 语句。版本 0.1.0 是有意发布到 PGXN 的版本,并且相对 0.0.1 进行了重大重写;README 仍将其标记为 pre-1.0,且未准备好用于生产。

激活

仅执行 CREATE EXTENSION 不会激活检查。SQL 文件只说明保护逻辑位于已加载的 shared library 中,因此每个 backend 都必须通过一种上游支持的路径加载 pg_savior

集群级激活使用 shared_preload_libraries,并需要重启 PostgreSQL:

shared_preload_libraries = 'pg_savior'

新连接的会话级激活可以在 config reload 后使用 session_preload_libraries

session_preload_libraries = 'pg_savior'

开发或测试会话可以手动加载 library:

LOAD 'pg_savior';
CREATE EXTENSION pg_savior;

library 加载后,_PG_init 会为该 backend 安装 post_parse_analyze_hookExecutorStart_hookProcessUtility_hook

DML 保护

pg_savior 会阻止没有 WHERE 子句的 DELETEUPDATE 语句。parser hook 检查分析后的 Query tree 并抛出 ERROR,因此事务会中止,应用会看到失败。

CREATE TABLE emp (id int);
INSERT INTO emp VALUES (1), (2), (3);

DELETE FROM emp;
-- ERROR: pg_savior: DELETE without WHERE clause is blocked

UPDATE emp SET id = id + 1;
-- ERROR: pg_savior: UPDATE without WHERE clause is blocked

DELETE FROM emp WHERE id = 1;
-- allowed

可选的行数保护适用于 planner estimate 超过 pg_savior.max_rows_affectedDELETEUPDATE 语句。它从 ExecutorStart_hook 运行,在规划之后、触碰 tuple 之前生效。

SET pg_savior.max_rows_affected = 100;

DELETE FROM emp WHERE id > 0;
-- blocked if the planner estimate is greater than 100 rows

DDL 保护

ProcessUtility_hook 只保护上游列出的 DDL 操作:

  • 没有 CONCURRENTLYCREATE INDEX 总是被阻止。
  • DROP DATABASE 总是被阻止。
  • 当目标表大于 pg_savior.large_table_threshold_rows 时,ALTER TABLE ADD COLUMN ... DEFAULT 被阻止。
  • 大表上的 ALTER TABLE ALTER COLUMN TYPE 被阻止。
  • 当任一目标表是大表时,TRUNCATE 被阻止。
  • 当任一目标表是大表时,DROP TABLE 被阻止。

大表检查使用 pg_class.reltuples > pg_savior.large_table_threshold_rows

CREATE INDEX emp_idx ON emp (id);
-- ERROR: pg_savior: CREATE INDEX without CONCURRENTLY is blocked

CREATE INDEX CONCURRENTLY emp_idx ON emp (id);
-- allowed by this guard

ALTER TABLE big_emp ADD COLUMN status text DEFAULT 'active';
-- blocked when big_emp is over pg_savior.large_table_threshold_rows

TRUNCATE big_emp;
-- blocked when big_emp is over pg_savior.large_table_threshold_rows

配置

所有已文档化的 GUC 都是 session-scoped USERSET 变量:

GUCDefaultEffect
pg_savior.enabledon总开关;为 off 时不运行检查。
pg_savior.bypassoff允许当前 session 跳过保护。
pg_savior.max_rows_affected0当估计的 DELETE/UPDATE 行数高于该值时阻止;0 禁用该检查。
pg_savior.large_table_threshold_rows1000000为受保护的大表 DDL 操作定义 “large”。

有意绕过一个事务时使用 SET LOCAL

BEGIN;
SET LOCAL pg_savior.bypass = on;
DELETE FROM staging_table;
COMMIT;

注意事项

  • 保护生效前,library 必须先在 backend 中加载;CREATE EXTENSION pg_savior 只注册扩展元数据。
  • 行数保护和大表保护依赖 planner/catalog estimate。近期变更导致 estimate 过期时,应运行 ANALYZE
  • UPDATE 覆盖范围限于无保护的 UPDATE 和可选 planner-estimate 阈值;README 未声称会语义审查每个 WHERE predicate。
  • DDL 覆盖范围限于列出的 ProcessUtility_hook case。不要假设其他 schema 变更会被阻止。
  • ADD COLUMN ... DEFAULT 保护较保守,会阻止大表上的任何 default,包括较新 PostgreSQL 版本可能无需 full table rewrite 处理的 non-volatile default。

最后修改 2026-05-01: update extension data (e399d22)