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

标签

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

时间: 2024-09-10 21:55:31

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

PostgreSQL 事件触发器应用 - DDL审计

标签 PostgreSQL , 事件触发器 , DDL审计 , 表结构变更 , 建表等审计 背景 DDL语句的审计是非常重要的,目前PG的DDL审计记录在日志文件中.不便于查看. 为了让DDL事件记录到表中,方便查看,我们可以通过事件触发器来达到这个效果. 事件触发器审计DDL操作 事件触发器语法: Command: CREATE EVENT TRIGGER Description: define a new event trigger Syntax: CREATE EVENT TRIGGER

PostgreSQL DDL事件触发器

标签 PostgreSQL , event trigger , ddl 背景 PostgreSQL 9.3 将引入事件触发器, 与普通触发器不同的是, 事件触发器是数据库全局的触发器, 可以由DDL事件来触发. 例如可以用来实施DDL的操作审计,以及防止某些用户执行某些DDL,在某些表上面执行DDL等等. Unlike regular triggers, which are attached to a single table and capture only DML events, event

PostgreSQL 10.0 逻辑复制原理与最佳实践

标签 PostgreSQL , logical replication , 逻辑复制 , 最佳实践 背景 PostgreSQL 从2010年发布的9.0开始支持流式物理复制,备库可以作为只读库打开,提供给用户使用. 物理复制的好处 1. 物理层面完全一致,这是许多商业数据库的惯用手段.例如Oracle的DG. 2. 延迟低,事务执行过程中产生REDO record,实时的在备库apply,事务结束时,备库立马能见到数据.不论事务多大,都一样. 3. 物理复制的一致性.可靠性达到了金融级的需求,不

ORACLE数据库DDL审计触发器与隐藏参数_system_trig_enabled

  最近,在我们开发库要对一套实例做一个DDL审计触发器,触发器代码如下所示: ---- 存储DDL语句的表          create table audit_ddl ( opertime timestamp PRIMARY KEY, ip varchar2(20), hostname varchar2(30), operation varchar2(30), object_type varchar2(30), object_name varchar2(30), sql_stmt clob

WPS三步打造演示统一母版的几个步骤

      WPS三步打造演示统一母版的几个步骤       Step1:对于PPT而言,设置统一的母版首先要在演示中打开"视图"选项卡,并选中"幻灯片母版". Step2:右侧显示出当前的母版样式,要更改只需要选中这张幻灯片母版,右键选择"新标题母版". Step3:现在便可以随意的设置自己母版样式啦.如果是为公司制作PPT,可以在幻灯片中加入公司的logo和网址.所有你添加的内容(如字体字号.版式.背景)都设置完毕后,点击"幻灯片母

任正非:将打造华为统一的AI平台,2018首先在GTS部署

本文来自华为总裁任正非在GTS人工智能实践进展汇报会上的讲话,任正非提出在公司内利用人工智能提升效率.解决实际问题的措施,加大对GTS数据系统.AI算法和AI 使能平台的投资,透露华为统一的AI平台将于2018年首先在GTS实践和应用. 聚焦内部效率提升,利用人工智能改变作业模式.简化管理,结合业务场景解决一线实际问题. 人工智能的核心在于应用,GTS把人工智能作为一个工具,研究海量重复性活动的智能化自动化,提升人的效率和辅助人的工作.从你们的探索来看,实践经验非常重要. 在人工智能和自动化推动

PostgreSQL 10.0 preview 功能增强 - 逻辑复制支持并行COPY初始化数据

标签 PostgreSQL , 10.0 , 逻辑复制 , 初始数据COPY 背景 PostgreSQL 已支持逻辑复制,同时对逻辑复制增加了一个初始同步的增强功能,支持通过wal receiver协议跑COPY命令(已封装在逻辑复制的内核代码中),支持多表并行. 也就是说,你可以使用PostgreSQL的逻辑复制,快速的(流式.并行)将一个实例迁移到另一个实例. Logical replication support for initial data copy Add functionalit

PostgreSQL 10.0 preview 变化 - 逻辑复制pg_hba.conf变化,不再使用replication条目

标签 PostgreSQL , 10.0 , 变化 , pg_hba.conf , replication , 逻辑复制 背景 pg_hba.conf的replication条目,在10.0后,将仅仅适用于物理复制. 逻辑复制使用普通DATABASE条目,但是逻辑复制的角色依旧需要带replication属性. 配置时请注意了. Change logical replication pg_hba.conf use Logical replication no longer uses the "r

给应用部分的js代码设定一个统一的入口_javascript技巧

javascript是种脚本语言,浏览器下载到哪儿就会执行到哪儿,这种特性会为编程提供方便,但也容易使程序过于凌乱,支离破碎. js从功能上可以分为两大部分--框架部分和应用部分,框架部分提供的是对js代码的组织作用,包括定义全局变量.命名空间方法等,每个页面都会有相同或类似的框架.应用部分提供的是页面功能逻辑,不同页面会有不同的功能,不同页面应用部分的代码也不尽相同. 给应用部分的js代码一个统一的入口,即: 复制代码 代码如下: <script type="text/javascrip