pg_event_trigger_ddl_commands() 是唯一能获取完整ddl对象信息的函数,必须配合 ddl_command_end 触发器使用,返回结构化数据用于审计;log_statement='ddl'仅记录语句文本,无法满足结构审计需求。

pg_event_trigger_ddl_commands() 是唯一能拿到完整 DDL 对象信息的函数,用它才能可靠记录建表、改字段、删索引这类操作。日志 log_statement = 'ddl' 只存语句文本,没法解析出对象类型或 schema 名,不满足结构审计需求。
必须用 ddl_command_end 触发点
只有 ddl_command_end 能调用 pg_event_trigger_ddl_commands() 获取结构化对象信息;ddl_command_start 返回空数组,sql_drop 只捕获删除动作且不带 command_tag,table_rewrite 仅限 VACUUM FULL / CLUSTER 等重写场景。
- 建表、加字段、改类型、删约束等都走
ddl_command_end - 触发器函数里必须用
SELECT * FROM pg_event_trigger_ddl_commands()拿结果集,不能只取一行 - 如果只处理单对象(比如只关心
TABLE),需在 INSERT 前加WHERE object_type = 'table'
log_ddl 表字段设计要匹配返回结构
pg_event_trigger_ddl_commands() 返回的 object_identity 是带双引号的全限定名(如 "public"."users"),而 schema_name 是裸名(public);command_tag 是字符串(CREATE TABLE),不是枚举值。
- 别把
object_identity当成可直接拼接 SQL 的字符串——它已转义,不能用于动态执行 -
objid和classid是 OID,查pg_class或pg_attribute时需类型转换,比如objid::regclass - 想记录字段级变更(如
ALTER TABLE ADD COLUMN),得从command字段传给pg_get_object_address()或解析object_identity后缀
函数里别直接用 current_query()
current_query() 在事件触发器中返回的是内部格式字符串(含参数占位符和换行符),不是用户输入的原始 SQL,且在并行 DDL 下可能为空或错乱。
- 真正需要原始语句时,应启用
log_statement = 'ddl'+ 日志采集,或用外部代理(如 pgbouncer 日志)补全 - 函数内优先用
tg_tag(即command_tag)和object_identity组合描述动作,例如tg_tag || ': ' || object_identity - 若坚持存语句,至少加
TRIM(BOTH '\n' FROM current_query())去首尾换行,避免插入失败
权限与部署顺序不能颠倒
事件触发器本身不校验执行者权限,但函数体里写入 log_ddl 表时,运行用户必须有 INSERT 权限;且触发器必须在函数创建之后、日志表存在之后才可创建。
- 先
CREATE TABLE log_ddl,再CREATE OR REPLACE FUNCTION f_trg_ddl(),最后CREATE EVENT TRIGGER trg_ddl ON ddl_command_end EXECUTE FUNCTION f_trg_ddl() - 函数属主建议设为超级用户或专用审计角色,避免普通用户 DROP/ALTER 触发器
- 生产环境务必加
WHEN tag IN ('CREATE TABLE', 'ALTER TABLE', 'DROP TABLE')过滤,否则COMMENT ON或GRANT也会触发,产生大量噪音
实际最难的不是写函数,是区分「谁在什么事务里改了哪个对象的哪部分结构」——pg_event_trigger_ddl_commands() 不提供事务 ID 或 xmin,得靠 txid_current() 手动打点,否则多语句批量 DDL 会丢失上下文。











