事件触发器概述
普通触发器(trigger)监听的是表上的行级或语句级 DML,而事件触发器(Event Trigger)监听的是整个数据库里发生的 DDL。它是 PostgreSQL 提供的一个「数据库级钩子」:只要有人执行 CREATE TABLE、ALTER INDEX、DROP FUNCTION 这类命令,你注册的函数就会被调用。用它可以在数据库内核之外实现审计、权限管控、命名规范强制、DDL 拦截等治理能力。
本页讲清事件触发器的定位、五个事件的生命周期、触发函数能拿到什么信息,以及它和普通触发器、规则(rule)之间的边界。这是后续深入学习 DDL 事件与表重写事件的地基。
基础概念
事件触发器是「数据库级对象」,不是表级对象
普通触发器依附于某一张表,表被删掉它也跟着消失。事件触发器不属于任何表、任何 schema,它挂在数据库本身之上,作用范围是当前数据库内的全部 DDL。
普通触发器(Trigger) 事件触发器(Event Trigger)
───────────────────────────── ─────────────────────────────
作用域:某一张表 作用域:整个数据库
监听:INSERT / UPDATE / DELETE / 监听:ddl_command_start /
TRUNCATE / TRUNCATE DDL ddl_command_end / sql_drop /
触发时机:行级(FOR EACH ROW) table_rewrite / login
语句级(FOR EACH STATEMENT) 触发时机:DDL 命令的前后
返回类型:trigger 返回类型:event_trigger
权限要求:表的所有者 权限要求:超级用户
存放在:pg_trigger 存放在:pg_event_trigger一句话概括:普通触发器管「数据怎么变」,事件触发器管「结构怎么变」。
五个触发事件
PostgreSQL 18 支持的事件触发器事件一共 5 个。官方原文:Currently, the supported events are login, ddl_command_start, ddl_command_end, table_rewrite and sql_drop.
| 事件 | 触发时机 | 可否用 WHEN TAG | 专用信息函数 |
|---|---|---|---|
login | 新会话建立时(客户端连接成功、后端进程启动后) | 否 | 无 |
ddl_command_start | DDL 命令执行之前 | 是 | 无(命令尚未执行) |
ddl_command_end | DDL 命令执行之后 | 是 | pg_event_trigger_ddl_commands() |
sql_drop | 有对象被 DROP 时,在 ddl_command_end 之前触发 | 是 | pg_event_trigger_dropped_objects() |
table_rewrite | 表因 DDL 被物理重写之前 | 是 | pg_event_trigger_table_rewrite_oid() / pg_event_trigger_table_rewrite_reason() |
注意一个反直觉的顺序:sql_drop 虽然名字里带 drop,但它在 ddl_command_end 之前触发。官方原文:The sql_drop event occurs just before the ddl_command_end event trigger for any operation that drops database objects.
一次 DROP TABLE 的完整生命周期
客户端: DROP TABLE orders;
│
├─► ① ddl_command_start 事件触发器触发
│ └─ 此时命令尚未执行,表还在,可以 raise exception 拦截
│
├─► ② PostgreSQL 执行 DROP TABLE
│
├─► ③ sql_drop 事件触发器触发
│ └─ pg_event_trigger_dropped_objects() 可列出被删对象(含级联删除的索引、约束)
│
└─► ④ ddl_command_end 事件触发器触发
└─ pg_event_trigger_ddl_commands() 可列出本次执行的 DDL 命令
│
└─► 事务提交(或回滚)这四个阶段全部发生在同一个事务里。也就是说:触发器函数里 RAISE EXCEPTION 会连带把那条 DDL 一起回滚——这正是「DDL 审计与拦截」能实现的原因。
与普通触发器、规则(rule)的对比
| 能力 | 普通触发器 | 事件触发器 | 规则(RULE) |
|---|---|---|---|
| 监听 DML | 是 | 否 | 是 |
| 监听 DDL | 否 | 是 | 否 |
| 能修改即将执行的数据 | 是(BEFORE 触发器中改 NEW) | 否 | 是(INSTEAD) |
| 能拦截并报错 | 是 | 是 | 有限 |
| 能拿到「被删对象的完整列表」 | 否 | 是 | 否 |
| 需要超级用户 | 否 | 是 | 否 |
| 能在备库上工作 | 取决于 session_replication_role | 取决于 evtenabled | 是 |
事件触发器存在哪:pg_event_trigger
SELECT oid, evtname, evtevent, evtowner, evtfoid, evtenabled, evttags
FROM pg_event_trigger;| 字段 | 类型 | 说明 |
|---|---|---|
oid | oid | 事件触发器 OID |
evtname | name | 事件触发器名称 |
evtevent | name | 监听的事件名,如 ddl_command_end |
evtowner | oid | 所有者(必然是超级用户),关联 pg_authid.oid |
evtfoid | oid | 被调用的函数,关联 pg_proc.oid |
evtenabled | char | 启用状态:O / D / R / A |
evttags | text[] | WHEN TAG IN (...) 里列出的命令标签;为空表示不过滤 |
evtenabled 的取值直接决定哪些 session_replication_role 下会真的触发:
| 值 | 含义 | 何时触发 |
|---|---|---|
O | Origin(默认) | 仅在 session_replication_role = origin(默认)时触发 |
D | Disabled | 永不触发 |
R | Replica | 仅在 session_replication_role = replica 时触发 |
A | Always | 无论 session_replication_role 是什么都触发 |
三个层次的「谁能拦住你」
应用层:GRANT / REVOKE、schema 权限 ← 拦住普通用户
事件触发器:RAISE EXCEPTION ← 拦住有 DDL 权限的角色
目录层:pg_catalog 与系统对象 ← 拦住所有人(包括超级用户)事件触发器是最后一道可编程防线,但它拦不住超级用户——超级用户可以直接 ALTER EVENT TRIGGER ... DISABLE。
语法与用法
-- 创建
CREATE EVENT TRIGGER name
ON event
[ WHEN TAG IN (filter_value [, ... ]) [ AND ... ] ]
EXECUTE FUNCTION function_name();
-- 改名
ALTER EVENT TRIGGER name RENAME TO new_name;
-- 启用 / 禁用(四态)
ALTER EVENT TRIGGER name ENABLE [ REPLICA | ALWAYS ];
ALTER EVENT TRIGGER name DISABLE;
-- 换函数
ALTER EVENT TRIGGER name ... -- 需 DROP 后重建,无法直接改函数
-- 改所有者
ALTER EVENT TRIGGER name OWNER TO { new_owner | CURRENT_ROLE | CURRENT_USER | SESSION_USER };
-- 删除
DROP EVENT TRIGGER name;最小可运行示例:
-- 1. 先写一个返回 event_trigger 的函数
CREATE FUNCTION log_ddl_event() RETURNS event_trigger
LANGUAGE plpgsql AS $$
BEGIN
RAISE NOTICE 'event = %, tag = %', TG_EVENT, TG_TAG;
END;
$$;
-- 2. 把它挂到 ddl_command_end 上
CREATE EVENT TRIGGER trg_log_ddl
ON ddl_command_end
EXECUTE FUNCTION log_ddl_event();postgres=# CREATE TABLE demo(id int);
NOTICE: event = ddl_command_end, tag = CREATE TABLE
CREATE TABLE管理用的常用查询:
-- 列出所有事件触发器及其函数、状态
SELECT e.evtname,
e.evtevent,
p.proname,
e.evtenabled,
e.evttags
FROM pg_event_trigger e
JOIN pg_proc p ON p.oid = e.evtfoid
ORDER BY e.evtname; evtname | evtevent | proname | evtenabled | evttags
-----------------+-------------------+------------------+------------+---------
trg_log_ddl | ddl_command_end | log_ddl_event | O |
(1 row)在 psql 里也可以用元命令快速查看:
postgres=# \dy
List of event triggers
Name | Event | Owner | Enabled | Function | Tags
-------------+-------------------+----------+---------+-----------+------
trg_log_ddl | ddl_command_end | postgres | enabled | log_ddl_event |
(1 row)详细说明
事件触发器是数据库级对象
事件触发器没有 schema 限定,也不归属某个 schema。你在 db_a 里建的事件触发器不会影响 db_b——事件触发器的作用范围恰好是「创建它的那个数据库」。
这带来两个直接结论:
- 每建一个新数据库,都要重新注册一遍。 事件触发器不会被
CREATE DATABASE继承(只有template1里的会被继承,因为新库是template1的拷贝)。 - 跨库治理必须逐库部署。 想全局统一,通常的做法是写一个幂等脚本,在
postgres库里对每个数据库执行。
-- 在 template1 里注册 = 让未来所有新库自动带上
\c template1
CREATE EVENT TRIGGER trg_audit_ddl ON ddl_command_end EXECUTE FUNCTION audit.record_ddl();同样地,pg_dump 不会导出事件触发器。官方文档在备份章节明确指出事件触发器不包含在 pg_dump 输出中,需要单独用脚本管理。
只有超级用户可以创建
CREATE EVENT TRIGGER 的权限要求非常硬:必须拥有超级用户权限。这与普通触发器「表的所有者就能建」完全不同。
-- 错误!
-- 以普通角色执行会直接失败
SET ROLE app_user;
CREATE EVENT TRIGGER trg_x ON ddl_command_end EXECUTE FUNCTION f();ERROR: permission denied to create event trigger "trg_x"
HINT: Must be superuser to create event triggers.原因很简单:事件触发器能看到并干预全库的 DDL,如果普通用户能建,那就能借此窃取所有 schema 的结构变更信息,也能用 RAISE EXCEPTION 搞拒绝服务。因此 PostgreSQL 把它牢牢锁在超级用户手里。改所有者(ALTER EVENT TRIGGER ... OWNER TO)也要求新所有者是超级用户。
函数必须返回 event_trigger
事件触发器的处理函数有两条硬性要求:
- 返回类型必须是
event_trigger(一个伪类型,pg_ddl_command的同族)。 - 不能声明任何参数(事件触发器调用时不会传参)。
-- 错误!返回类型不对
CREATE FUNCTION bad_1() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN RETURN NEW; END; $$;
CREATE EVENT TRIGGER t1 ON ddl_command_end EXECUTE FUNCTION bad_1();ERROR: function bad_1 must return type event_trigger-- 错误!带了参数
CREATE FUNCTION bad_2(x int) RETURNS event_trigger LANGUAGE plpgsql AS $$ BEGIN END; $$;ERROR: function bad_2 must have no arguments函数体内部的返回值会被忽略——RETURN NULL; 是标准写法。你不能通过返回值做任何「修改」,拦截只能靠 RAISE EXCEPTION。
五个触发事件各自的语义
| 事件 | 官方语义 | 典型用途 |
|---|---|---|
login | 新会话建立时触发 | 连接审计、连接数治理、会话级环境初始化 |
ddl_command_start | DDL 命令执行前触发 | 事前拦截(禁止建表、禁止 DROP) |
ddl_command_end | DDL 命令执行后触发 | 事后审计、自动补 COMMENT、同步结构到外部系统 |
sql_drop | 有对象被删除时触发,先于 ddl_command_end | 删除审计、清理关联资源 |
table_rewrite | 表被重写前触发 | 预警大表重写、限制重写窗口 |
login 事件是较新加入的成员,它与其余四个有本质区别:它不属于 DDL 生命周期,因此既不能用 WHEN TAG 过滤,也不能调用 pg_event_trigger_ddl_commands() 之类的信息函数。它的触发点非常靠前(会话初始化阶段),这意味着:
- 在
login触发器里做重活会拖慢每一次连接; - 一旦函数报错,连接会直接失败,等于把自己锁在门外;
- 它可以用 GUC
event_triggers = false或ALTER EVENT TRIGGER ... DISABLE绕过。
WHEN TAG 的过滤规则
WHEN TAG IN (...) 里写的是命令标签(command tag),也就是 psql 执行完命令后打印的那个大写字符串,例如 CREATE TABLE、ALTER TABLE、DROP INDEX。
CREATE EVENT TRIGGER trg_audit_create
ON ddl_command_end
WHEN TAG IN ('CREATE TABLE', 'CREATE TABLE AS', 'SELECT INTO')
EXECUTE FUNCTION audit_create();要点:
- 标签区分大小写,必须写成大写形式;写小写不会报错,但永远匹配不上。
- 一个事件触发器的
WHEN子句可以写多个TAG IN (...)组,组间是AND(交集),组内是OR(并集)。实际上多组TAG IN交集基本得不到结果,常用形式是单组。 login事件不支持WHEN TAG。- 注意
SELECT INTO在内部会被当作CREATE TABLE AS处理,标签过滤时要留意官方文档列出的对应关系。
-- 不推荐:多组 TAG IN 取交集,几乎永远不触发
CREATE EVENT TRIGGER trg_x ON ddl_command_end
WHEN TAG IN ('CREATE TABLE') AND TAG IN ('DROP TABLE')
EXECUTE FUNCTION f();生命周期里能拿到什么信息
不同的阶段能拿到的信息不同,这是最容易踩坑的地方:
| 事件 | 可用的信息函数 | 拿不到什么 |
|---|---|---|
login | 无 | 没有任何 DDL 上下文 |
ddl_command_start | 可用 TG_TAG,可读系统目录的「旧状态」 | 命令尚未执行,看不到新对象 |
ddl_command_end | pg_event_trigger_ddl_commands() | 看不到「被删对象的级联清单」(用 sql_drop) |
sql_drop | pg_event_trigger_dropped_objects() | 看不到「命令文本」 |
table_rewrite | pg_event_trigger_table_rewrite_oid() / ..._reason() | 没有通用的对象清单 |
在 plpgsql 里还有几个专用变量:
| 变量 | 类型 | 说明 |
|---|---|---|
TG_EVENT | text | 触发的事件名,如 ddl_command_end |
TG_TAG | text | 触发本次调用的命令标签,如 CREATE TABLE |
CREATE FUNCTION audit_tag() RETURNS event_trigger LANGUAGE plpgsql AS $$
BEGIN
RAISE NOTICE 'event=%, tag=%', TG_EVENT, TG_TAG;
END;
$$;evtenabled 与 session_replication_role 的配合
这是事件触发器最像普通触发器的地方。普通触发器的「点火」由 session_replication_role 决定,事件触发器也一样:O 只在 origin 下生效,R 只在 replica 下生效,A 永远生效。
-- 让某个触发器在逻辑复制/恢复场景下也执行
ALTER EVENT TRIGGER trg_audit ENABLE ALWAYS;这个设计的意义在于:pg_restore、逻辑复制订阅端常常会把 session_replication_role 设为 replica 以跳过触发器(避免重复触发副作用)。如果你的事件触发器是「审计」性质的、希望任何情况下都记录,就应该设为 ALWAYS;如果它是「管控」性质的(例如禁止建表),那么设成 ALWAYS 会让恢复流程也被拦住,需要谨慎。
-- 会话级切换(需超级用户)
SET session_replication_role = replica;
-- 此后只有 evtenabled IN ('R','A') 的事件触发器会触发
RESET session_replication_role;事务性与回滚行为
事件触发器运行在触发它的那条 DDL 所在的事务里。因此:
- 触发器中的
RAISE EXCEPTION→ 整条 DDL 回滚,报错返回给客户端。 - 触发器写入审计表 → 该写入与 DDL 同生共死:DDL 回滚时审计记录也一起回滚。
CREATE FUNCTION deny_drop() RETURNS event_trigger LANGUAGE plpgsql AS $$
BEGIN
RAISE EXCEPTION 'DROP 已被策略禁止 (tag=%)', TG_TAG;
END;
$$;postgres=# DROP TABLE demo;
ERROR: DROP 已被策略禁止 (tag=DROP TABLE)第二条性质常被误用:有人希望「审计日志即使 DDL 失败也要留下记录」,但在同一个事务里做不到。要实现这种「失败也要留痕」的需求,只能走外部通道:写入另一台库、写文件、发消息队列,或者干脆让 login/ddl_command_start 阶段的 RAISE WARNING 由日志收集系统捕获。
事件触发器不能做什么
- 不能在单用户模式(single-user mode)下创建或执行。 官方原文:Event triggers are disabled in single-user mode (see
postgres) as well as whenevent_triggersis set tofalse. 这提供了逃生通道:如果某条 DDL 被事件触发器锁死,可以通过event_triggers=false或单用户模式绕开。 - 不能在已中止的事务中执行。 官方说明:Event triggers (like other functions) cannot be executed in an aborted transaction.
- 不能修改即将执行的 DDL。 没有「改
NEW」这种机制,只能放行或报错。 - 不能监听 DML。
INSERT/UPDATE/DELETE必须用普通触发器。 pg_event_trigger_ddl_commands()等函数有调用上下文限制,在错误的事件里调用会直接报错,见下一节ddl-events。
实战示例
示例一:建一张 DDL 审计表 + 全量审计触发器
-- 审计表单独放一个 schema,业务角色只读
CREATE SCHEMA IF NOT EXISTS audit;
CREATE TABLE audit.ddl_log (
id bigserial PRIMARY KEY,
logged_at timestamptz NOT NULL DEFAULT clock_timestamp(),
db_name text NOT NULL DEFAULT current_database(),
session_user_name text NOT NULL DEFAULT session_user,
current_role_name text NOT NULL DEFAULT current_user,
event text NOT NULL,
tag text NOT NULL,
object_type text,
object_id text,
command text
);
CREATE OR REPLACE FUNCTION audit.record_ddl() RETURNS event_trigger
LANGUAGE plpgsql SECURITY DEFINER AS $$
DECLARE
r record;
BEGIN
FOR r IN SELECT * FROM pg_event_trigger_ddl_commands() LOOP
INSERT INTO audit.ddl_log(event, tag, object_type, object_id, command)
VALUES (TG_EVENT, r.command_tag, r.object_type,
r.object_identity, r.command::text);
END LOOP;
END;
$$;
CREATE EVENT TRIGGER trg_audit_ddl
ON ddl_command_end
EXECUTE FUNCTION audit.record_ddl();postgres=# CREATE INDEX idx_demo_a ON demo(a);
CREATE INDEX
postgres=# SELECT tag, object_type, object_id FROM audit.ddl_log ORDER BY id;
tag | object_type | object_id
--------------+-------------+------------
CREATE INDEX | index | public.idx_demo_a
(1 row)注意 pg_event_trigger_ddl_commands() 会被循环遍历:一条 DDL 可能产生多行(例如 ALTER TABLE 同时改了多个对象),所以触发器函数必须写成 FOR r IN SELECT ... LOOP。
示例二:禁止在业务 schema 里执行 DROP
CREATE OR REPLACE FUNCTION guard.no_drop_in_app() RETURNS event_trigger
LANGUAGE plpgsql AS $$
DECLARE
obj record;
BEGIN
FOR obj IN SELECT * FROM pg_event_trigger_dropped_objects()
WHERE schema_name = 'app' LOOP
RAISE EXCEPTION '禁止删除 app schema 中的对象:% (%)',
obj.object_identity, obj.object_type;
END LOOP;
END;
$$;
CREATE EVENT TRIGGER trg_no_drop_in_app
ON sql_drop
EXECUTE FUNCTION guard.no_drop_in_app();app=# DROP TABLE app.customers;
ERROR: 禁止删除 app schema 中的对象:app.customers (table)这里能生效的关键是 pg_event_trigger_dropped_objects() 提供了 schema_name 和 object_identity——它比 TG_TAG 精确得多,因为 TG_TAG 只能告诉你「这是个 DROP 命令」,不会告诉你删的是哪个 schema 的什么对象。
示例三:巡检当前库里的事件触发器
SELECT e.evtname,
e.evtevent,
e.evtenabled,
COALESCE(array_to_string(e.evttags, ', '), '(all)') AS tags,
n.nspname || '.' || p.proname AS func,
p.prolang::regtype AS lang
FROM pg_event_trigger e
JOIN pg_proc p ON p.oid = e.evtfoid
JOIN pg_namespace n ON n.oid = p.pronamespace
ORDER BY e.evtevent, e.evtname; evtname | evtevent | evtenabled | tags | func | lang
------------------+------------------+------------+-------------+--------------------------+----------
trg_audit_ddl | ddl_command_end | O | (all) | audit.record_ddl | plpgsql
trg_no_drop_in_app | sql_drop | O | (all) | guard.no_drop_in_app | plpgsql
(2 rows)这个查询建议纳入日常巡检:事件触发器是「隐形」的全局行为,一旦有人偷偷建了一个报错型触发器,整个库的 DDL 都会莫名其妙失败,而报错信息往往只提示「函数返回了错误」,不告诉你是哪个事件触发器干的。
示例四:临时禁用排查问题
-- 先禁用可疑触发器再重试
ALTER EVENT TRIGGER trg_no_drop_in_app DISABLE;
DROP TABLE app.customers;
-- 排查完成后恢复
ALTER EVENT TRIGGER trg_no_drop_in_app ENABLE;注意事项
不要以为普通用户能创建事件触发器。 官方要求超级用户,普通角色会得到
permission denied to create event trigger。不要忘了事件触发器是「每库一份」。 在
postgres库里建的不会自动出现在业务库里,也不会被pg_dump带出去。sql-- 不推荐:以为在 postgres 库建完就全库生效了 \c postgres CREATE EVENT TRIGGER trg_audit ON ddl_command_end EXECUTE FUNCTION audit.record_ddl(); \c app_db -- 这里没有触发器 \dy不要在
ddl_command_start里调用pg_event_trigger_ddl_commands()。 该函数只允许在ddl_command_end中调用,其他地方一律报错。sql-- 错误! CREATE FUNCTION bad() RETURNS event_trigger LANGUAGE plpgsql AS $$ DECLARE n int; BEGIN SELECT count(*) INTO n FROM pg_event_trigger_ddl_commands(); END; $$; CREATE EVENT TRIGGER t_bad ON ddl_command_start EXECUTE FUNCTION bad(); -- DROP TABLE demo; → ERROR: pg_event_trigger_ddl_commands() can only be called in a ddl_command_end event trigger function不要用
pg_event_trigger_dropped_objects()去ddl_command_end里找被删对象。 它是sql_drop专用的。不要假设
TG_TAG是「命令原文」。 它是命令标签,不含对象名和参数。想看完整 SQL 文本,得从ddl_command_end的command列取。不要让触发器函数的返回值承担逻辑。 返回值被忽略,写
RETURN NEW之类的代码只会让人误读。不要在事件触发器里做重活(尤其是
login)。 它在路径的极前端执行,任何延迟都被放大到每一次 DDL / 每一次连接。sql-- 不推荐:在 login 触发器里查询大表 CREATE FUNCTION heavy_login() RETURNS event_trigger LANGUAGE plpgsql AS $$ BEGIN PERFORM count(*) FROM audit.huge_events; -- 拖慢每一个连接 END; $$;不要把「审计」触发器设成
ALWAYS之后又依赖它做管控。 两者目标相反,混用会让pg_restore与逻辑复制行为难以预测。不要指望在触发器中「修改」即将执行的 DDL。 没有可写的上下文对象,只能放行或中止。
不要在触发器函数里依赖事务外的副作用来「留痕」。 同事务写入会随 DDL 一起回滚。
不要忘记单用户模式与
event_triggers=false是逃生通道。 被自己写的触发器锁死时,postgres --single或设置event_triggers = false可以进去把它删掉。
最佳实践
事件触发器统一放在专门的 schema 下,函数名与触发器名保持一对一可追溯。
sqlCREATE SCHEMA IF NOT EXISTS audit; CREATE EVENT TRIGGER trg_audit_ddl ON ddl_command_end EXECUTE FUNCTION audit.record_ddl();用
SECURITY DEFINER+ 严格search_path保护审计写入。 审计表不应给业务角色写权限。sqlCREATE FUNCTION audit.record_ddl() RETURNS event_trigger LANGUAGE plpgsql SECURITY DEFINER SET search_path = pg_catalog, audit AS $$ ... $$;用
WHEN TAG IN (...)缩小监听范围。 全量监听会为每条 DDL 付出一次函数调用开销。sqlCREATE EVENT TRIGGER trg_audit_ddl ON ddl_command_end WHEN TAG IN ('CREATE TABLE', 'ALTER TABLE', 'DROP TABLE', 'CREATE INDEX', 'DROP INDEX') EXECUTE FUNCTION audit.record_ddl();把「审计」与「管控」写成两个独立的事件触发器。 审计要
ALWAYS(宽),管控要O(窄),职责分开才好调。拦截型触发器给出可操作的错误提示。 不要只写「禁止操作」,把
object_identity、命令标签、正确的申请流程写进HINT。sqlRAISE EXCEPTION '禁止在 app schema 中 DROP 对象:%', obj.object_identity USING HINT = '请走 change request 流程,由 DBA 在维护窗口执行';用幂等脚本部署事件触发器,纳入版本管理。
CREATE EVENT TRIGGER没有IF NOT EXISTS,脚本里先判断再建。sqlDO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_event_trigger WHERE evtname = 'trg_audit_ddl') THEN CREATE EVENT TRIGGER trg_audit_ddl ON ddl_command_end EXECUTE FUNCTION audit.record_ddl(); END IF; END $$;新库初始化时自动装载。 把注册逻辑放进
template1或放进数据库初始化流水线。审计表按时间分区并定期归档。 DDL 日志增长慢但不会停,用
pg_partman或原生分区按月切。给
login事件触发器严格限速与限时。 它服务于连接路径,任何查询都必须走索引且代价可控。部署前先在测试库验证触发器不会误伤
pg_dump/pg_restore/ 迁移工具。 很多迁移框架会执行大量 DDL,一个写错的管控触发器能让整个部署失败。bash# 在测试库演练恢复,确认没有事件触发器拦截 pg_restore -d test_db -j 4 dump.custom把事件触发器清单纳入监控与变更审计。 没人记得的隐形全局钩子是生产事故的常见来源。
sqlSELECT evtname, evtevent, evtenabled FROM pg_event_trigger ORDER BY 1;
下一节
下一节:DDL 事件触发(CREATE / ALTER / DROP)——讲清 ddl_command_start / ddl_command_end / sql_drop 三个事件的细节,以及 pg_event_trigger_ddl_commands() 与 pg_event_trigger_dropped_objects() 的完整字段与用法。
参考链接
- PostgreSQL 18 官方文档:Event Triggers(概念性概述)
- PostgreSQL 18 官方文档:Overview of Event Trigger Behavior
- PostgreSQL 18 官方文档:CREATE EVENT TRIGGER
- PostgreSQL 18 官方文档:ALTER EVENT TRIGGER
- PostgreSQL 18 官方文档:DROP EVENT TRIGGER
- PostgreSQL 18 官方文档:Event Trigger Functions(信息函数)
- PostgreSQL 18 官方文档:pg_event_trigger(系统目录)
- PostgreSQL 18 官方文档:PL/pgSQL Trigger Functions(
TG_EVENT/TG_TAG)