DDL审计 , DDL逻辑复制 , 打造DDL统一管理入口 - PostgreSQL DDL 事件触发器应用

本文涉及的产品
云原生多模数据库 Lindorm,多引擎 多规格 0-4节点
云数据库 MongoDB,独享型 2核8GB
推荐场景:
构建全方位客户视图
云数据库 Tair(兼容Redis),内存型 2GB
简介:

标签

PostgreSQL , event trigger , 事件触发器 , 用法详解


背景

DDL是非常重的操作,(锁大,或者会导致TABLE REWRITE导致消耗大量资源,影响大,例如DROP,TRUNCATE)也是数据库的使用过程中需要关注的。

通常企业会对DDL做一定的限制,不允许某些用户执行,或者不允许从网络登录的用户执行DDL。

同时DDL还有被审计的需求,所有的DDL可能都要记录到日志中。

构建逻辑复制的备库时,由于DDL不记录在REDO中,所以如果要复制DDL,通常的做法是使用事件触发器,将DDL记录到表里面,然后使用这张表产生的REDO解析出DDL语句。

(当然,随着PostgreSQL支持自定义WAL record,直接的DDL的复制也逐渐成为可能了)

最后就是DBA也有一种需求,比如收集DDL,统一审计和执行。用户在执行DDL时,并不会真的执行,而是记录在

事件触发器在这种需求中担当了很好的角色。

例子

如果你要记录用户表被DDL修改定义, 设置默认值, 等等alter table可以完成的工作时的记录, 可以使用事件触发器来达到这个目的.

例子如下 :

postgres=# create extension hstore;  

postgres=# create or replace function ef_alter() returns event_trigger as $$  
declare  
  rec hstore;  
begin  
  select hstore(pg_stat_activity.*) into rec from pg_stat_activity where pid=pg_backend_pid();  -- 记录pg_stat_activity的内容  
  insert into aud_alter (ctx) values (rec);  
end;  
$$ language plpgsql strict;  
CREATE FUNCTION  

postgres=# create event trigger e_alter on ddl_command_end when tag in ('ALTER TABLE') execute procedure ef_alter();  
CREATE EVENT TRIGGER  

postgres=# create table aud_alter(id serial primary key, crt_time timestamp default now(), ctx hstore);  
CREATE TABLE  

postgres=# create table test(id int);  
CREATE TABLE  

postgres=# alter table test alter column id type int8;  
ALTER TABLE  

postgres=# select * from aud_alter;  
 id |          crt_time          |                                                                                                    

       ctx                                                                                                                            

----+----------------------------+------------   
  1 | 2014-12-12 05:43:42.840327 | "pid"=>"48406", "datid"=>"12949", "query"=>"alter table test alter column id type int8;", "state"  
=>"active", "datname"=>"postgres", "usename"=>"postgres", "waiting"=>"f", "usesysid"=>"10", "xact_start"=>"2014-12-12 05:43:42.84032  
7+08", "client_addr"=>NULL, "client_port"=>"-1", "query_start"=>"2014-12-12 05:43:42.840327+08", "state_change"=>"2014-12-12 05:43:4  
2.840331+08", "backend_start"=>"2014-12-12 05:38:37.084733+08", "client_hostname"=>NULL, "application_name"=>"psql"  
(1 row)  


postgres=# select each(ctx) from aud_alter where id=1;  
                         each                            
-------------------------------------------------------  
 (pid,48406)  
 (datid,12949)  
 (query,"alter table test alter column id type int8;")  
 (state,active)  
 (datname,postgres)  
 (usename,postgres)  
 (waiting,f)  
 (usesysid,10)  
 (xact_start,"2014-12-12 05:43:42.840327+08")  
 (client_addr)  
 (client_port,-1)  
 (query_start,"2014-12-12 05:43:42.840327+08")  
 (state_change,"2014-12-12 05:43:42.840331+08")  
 (backend_start,"2014-12-12 05:38:37.084733+08")  
 (client_hostname)  
 (application_name,psql)  
(16 rows)  

query即当时的ALTER TABLE SQL.

其他辅助的还有用户当时的IP, PORT, 用户, 链接的数据库等信息.

如果你不想让它真正的被执行,只想记录下来.

create or replace function ef_alter() returns event_trigger as $$  
declare  
  rec hstore;  
  v1 int8;  
  v2 oid;  
  v3 text;  
  v4 text;  
  v5 text;  
  v6 text;  
  v7 text;  
  v8 text;  
  v9 text;  
  v10 text;  
  v11 text;  
  v12 text;  
  v13 text;  
  r record;  
begin  
  GET DIAGNOSTICS v1 = ROW_COUNT,  
                  v2 = RESULT_OID,  
                  v3 = PG_CONTEXT;  
  RAISE NOTICE 'ROW_COUNT:%, RESULT_OID:%, PG_CONTEXT:%', v1,v2,v3;  

if TG_EVENT='ddl_command_end' then  
    FOR r IN SELECT * FROM pg_event_trigger_ddl_commands() LOOP  
        RAISE NOTICE 'classid:%, objid:%, objsubid:%, command_tag:%, object_type:%, schema_name:%, object_identity:%, in_extension:%, command:%',   
      r.classid, r.objid, r.objsubid, r.command_tag, r.object_type, r.schema_name, r.object_identity, r.in_extension, r.command;  
    END LOOP;  
end if;  

if TG_EVENT='sql_drop' then  
    FOR r IN SELECT * FROM pg_event_trigger_dropped_objects() LOOP  
        RAISE NOTICE 'classid:%, objid:%, objsubid:%, original:%, normal:%, is_temporary:%, object_type:%, schema_name:%, object_name:%, object_identity:%, address_names:%, address_args:%',   
      r.classid, r.objid, r.objsubid, r.original, r.normal, r.is_temporary, r.object_type, r.schema_name, r.object_name, r.object_identity, r.address_names, r.address_args;  
    END LOOP;  
end if;  

if TG_EVENT='table_rewrite' then  
    RAISE NOTICE 'rewriting table % for reason %',  
                pg_event_trigger_table_rewrite_oid()::regclass,  
                pg_event_trigger_table_rewrite_reason();  
end if;  

  raise exception 'error';  

  exception when others then  
    select hstore(pg_stat_activity.*) into rec from pg_stat_activity where pid=pg_backend_pid();  -- 记录pg_stat_activity的内容  
    -- insert into aud_alter (ctx) values (rec);  -- 这条并不能提交,如果要提交,可以使用dblink代替.
    -- 如果要记录,使用dblink即可. 
    raise notice '%', rec;  

    GET STACKED DIAGNOSTICS v4 = RETURNED_SQLSTATE,  
                            v5 = COLUMN_NAME,  
                            v6 = CONSTRAINT_NAME,  
                            v7 = PG_DATATYPE_NAME,  
                            v8 = MESSAGE_TEXT,  
                            v9 = TABLE_NAME,  
                            v10 = SCHEMA_NAME,  
                            v11 = PG_EXCEPTION_DETAIL,  
                            v12 = PG_EXCEPTION_HINT,  
                            v13 = PG_EXCEPTION_CONTEXT;  

    RAISE NOTICE 'RETURNED_SQLSTATE:%, COLUMN_NAME:%, CONSTRAINT_NAME:%, PG_DATATYPE_NAME:%, MESSAGE_TEXT:%, TABLE_NAME:%, SCHEMA_NAME:%, PG_EXCEPTION_DETAIL:%, PG_EXCEPTION_HINT:%, PG_EXCEPTION_CONTEXT:%',   
      v4,v5,v6,v7,v8::text,v9,v10,v11,v12,v13;  
    raise exception 'error';  -- 回滚
end;  
$$ language plpgsql strict;  

create event trigger e1 on ddl_command_end  execute procedure ef_alter();  

create event trigger e2 on sql_drop  execute procedure ef_alter();  

create event trigger e3 on table_rewrite  execute procedure ef_alter();  


postgres=# drop table image;
NOTICE:  ROW_COUNT:0, RESULT_OID:0, PG_CONTEXT:PL/pgSQL function ef_alter() line 19 at GET DIAGNOSTICS
NOTICE:  classid:1259, objid:253698, objsubid:0, original:t, normal:f, is_temporary:f, object_type:table, schema_name:public, object_name:image, object_identity:public.image, address_names:{public,image}, address_args:{}
NOTICE:  classid:1247, objid:253700, objsubid:0, original:f, normal:f, is_temporary:f, object_type:type, schema_name:public, object_name:image, object_identity:public.image, address_names:{public.image}, address_args:{}
NOTICE:  classid:1247, objid:253699, objsubid:0, original:f, normal:f, is_temporary:f, object_type:type, schema_name:public, object_name:_image, object_identity:public.image[], address_names:{public.image[]}, address_args:{}
NOTICE:  classid:1259, objid:253701, objsubid:0, original:f, normal:f, is_temporary:f, object_type:toast table, schema_name:pg_toast, object_name:pg_toast_253698, object_identity:pg_toast.pg_toast_253698, address_names:{pg_toast,pg_toast_253698}, address_args:{}
NOTICE:  classid:1259, objid:253703, objsubid:0, original:f, normal:f, is_temporary:f, object_type:index, schema_name:pg_toast, object_name:pg_toast_253698_index, object_identity:pg_toast.pg_toast_253698_index, address_names:{pg_toast,pg_toast_253698_index}, address_args:{}
NOTICE:  classid:1247, objid:253702, objsubid:0, original:f, normal:f, is_temporary:f, object_type:type, schema_name:pg_toast, object_name:pg_toast_253698, object_identity:pg_toast.pg_toast_253698, address_names:{pg_toast.pg_toast_253698}, address_args:{}
NOTICE:  "pid"=>"93404", "datid"=>"13269", "query"=>"drop table image;", "state"=>"active", "datname"=>"postgres", "usename"=>"postgres", "usesysid"=>"10", "wait_event"=>NULL, "xact_start"=>"2016-12-07 16:16:56.795366+08", "backend_xid"=>"436698321", "client_addr"=>"127.0.0.1", "client_port"=>"23404", "query_start"=>"2016-12-07 16:16:56.795366+08", "backend_xmin"=>"436698321", "state_change"=>"2016-12-07 16:16:56.79537+08", "backend_start"=>"2016-12-07 16:06:23.205288+08", "client_hostname"=>NULL, "wait_event_type"=>NULL, "application_name"=>"psql"
NOTICE:  RETURNED_SQLSTATE:P0001, COLUMN_NAME:, CONSTRAINT_NAME:, PG_DATATYPE_NAME:, MESSAGE_TEXT:error, TABLE_NAME:, SCHEMA_NAME:, PG_EXCEPTION_DETAIL:, PG_EXCEPTION_HINT:, PG_EXCEPTION_CONTEXT:PL/pgSQL function ef_alter() line 44 at RAISE
ERROR:  error
CONTEXT:  PL/pgSQL function ef_alter() line 64 at RAISE

postgres=# \d image  -- 未删除, 但是从raise中可以获取query.  
   Table "public.image"
 Column | Type | Modifiers 
--------+------+-----------
 name   | text | 
 raster | oid  |  

如果只想跟踪表的字段被修改的前后类型, 更严格的做法应该是从parse tree中取出被修改的字段, 类型.

参考

http://www.postgresql.org/docs/9.3/static/event-trigger-interface.html

typedef struct EventTriggerData  
{  
    NodeTag     type;  
    const char *event;      /* event name */  
    Node       *parsetree;  /* parse tree */  
    const char *tag;        /* command tag */  
} EventTriggerData;  

参考

1. http://www.postgresql.org/docs/9.3/static/event-triggers.html

2. http://www.postgresql.org/docs/9.3/static/sql-createeventtrigger.html

3. src/backend/commands/event_trigger.c

4. src/include/commands/event_trigger.h

5. http://blog.163.com/digoal@126/blog/static/163877040201252575529358/

6. http://stackoverflow.com/questions/23488228/how-to-get-sql-text-from-postgres-event-trigger

7. https://www.postgresql.org/docs/9.6/static/plpgsql-control-structures.html#PLPGSQL-ERROR-TRAPPING

8. https://www.postgresql.org/docs/9.6/static/plpgsql-statements.html#PLPGSQL-STATEMENTS-DIAGNOSTICS

相关实践学习
使用PolarDB和ECS搭建门户网站
本场景主要介绍基于PolarDB和ECS实现搭建门户网站。
阿里云数据库产品家族及特性
阿里云智能数据库产品团队一直致力于不断健全产品体系,提升产品性能,打磨产品功能,从而帮助客户实现更加极致的弹性能力、具备更强的扩展能力、并利用云设施进一步降低企业成本。以云原生+分布式为核心技术抓手,打造以自研的在线事务型(OLTP)数据库Polar DB和在线分析型(OLAP)数据库Analytic DB为代表的新一代企业级云原生数据库产品体系, 结合NoSQL数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
目录
相关文章
|
6月前
|
存储 前端开发 JavaScript
简单实现一个事件触发器
简单实现一个事件触发器
84 0
|
6月前
|
存储 Oracle 关系型数据库
postgresql数据库|wal日志的开启以及如何管理
postgresql数据库|wal日志的开启以及如何管理
1083 0
|
3月前
|
消息中间件 运维 Serverless
函数计算产品使用问题之如何判断从函数调用获取到的事件是由哪个触发器发出的
函数计算产品作为一种事件驱动的全托管计算服务,让用户能够专注于业务逻辑的编写,而无需关心底层服务器的管理与运维。你可以有效地利用函数计算产品来支撑各类应用场景,从简单的数据处理到复杂的业务逻辑,实现快速、高效、低成本的云上部署与运维。以下是一些关于使用函数计算产品的合集和要点,帮助你更好地理解和应用这一服务。
|
4月前
|
关系型数据库 分布式数据库 数据库
PolarDB产品使用问题之如何进行PostgreSQL(简称PG)的全量和增量备份管理
PolarDB产品使用合集涵盖了从创建与管理、数据管理、性能优化与诊断、安全与合规到生态与集成、运维与支持等全方位的功能和服务,旨在帮助企业轻松构建高可用、高性能且易于管理的数据库环境,满足不同业务场景的需求。用户可以通过阿里云控制台、API、SDK等方式便捷地使用这些功能,实现数据库的高效运维与持续优化。
|
5月前
|
存储 关系型数据库 数据库
经验大分享:PostgreSQL学习之【用户权限管理】说明
经验大分享:PostgreSQL学习之【用户权限管理】说明
43 0
|
5月前
|
运维 Serverless 数据处理
函数计算产品使用问题之OSS触发器是否只支持事件处理函数
函数计算产品作为一种事件驱动的全托管计算服务,让用户能够专注于业务逻辑的编写,而无需关心底层服务器的管理与运维。你可以有效地利用函数计算产品来支撑各类应用场景,从简单的数据处理到复杂的业务逻辑,实现快速、高效、低成本的云上部署与运维。以下是一些关于使用函数计算产品的合集和要点,帮助你更好地理解和应用这一服务。
|
5月前
|
存储
数字逻辑与模拟电子技术-部分知识点(4)——数电部分-组合电路的一般分析和设计方法、三人和四人表决器的设计、SR触发器、D触发器、JK触发器
数字逻辑与模拟电子技术-部分知识点(4)——数电部分-组合电路的一般分析和设计方法、三人和四人表决器的设计、SR触发器、D触发器、JK触发器
86 0
|
6月前
|
SQL 存储 缓存
PostgreSQL函数管理接口
学习PostgreSQL服务端开发必须要对函数管理接口有比较深入的了解
|
6月前
|
SQL 数据库
10. 触发器和事件
10. 触发器和事件
|
6月前
|
关系型数据库 数据库 PostgreSQL