Postgresql 创建日志模式

Posted

技术标签:

【中文标题】Postgresql 创建日志模式【英文标题】:Postgresql create a log schema 【发布时间】:2016-01-29 08:02:16 【问题描述】:

所以我的问题很简单。我有一个架构prod 有很多表,另一个log 有完全相同的表和结构(主键改变就是这样)。 当我在架构prod 中执行UPDATEDELETE 时,我想在log 架构中记录旧数据。

我在更新或删除后调用了以下函数:

CREATE FUNCTION prod.log_data() RETURNS trigger
LANGUAGE plpgsql AS $$
DECLARE
    v RECORD;
    column_names text;
    value_names text;
BEGIN

    -- get column names of current table and store the list in a text var
    column_names = '';
    value_names = '';
    FOR v IN SELECT * FROM information_schema.columns WHERE table_name = quote_ident(TG_TABLE_NAME) AND table_schema = quote_ident(TG_TABLE_SCHEMA) LOOP
        column_names = column_names || ',' || v.column_name;
        value_names = value_names || ',$1.' || v.column_name;
    END LOOP;

    -- remove first char ','
    column_names = substring( column_names FROM 2);
    value_names = substring( value_names FROM 2);

    -- execute the insert into log schema
    EXECUTE 'INSERT INTO log.' || TG_TABLE_NAME || ' ( ' || column_names || ' ) VALUES ( ' || value_names || ' )' USING OLD;

    RETURN NULL; -- no need to return, it is executed after update
END;$$;

烦人的部分是我必须从information_schema 获取每一行的列名。 我宁愿用这个:

    EXECUTE 'INSERT INTO log.' || TG_TABLE_NAME || ' SELECT ' || OLD;

但有些值可以是NULL,所以这会执行:

INSERT INTO log.user SELECT 2,,,"2015-10-28 13:52:44.785947" 代替 INSERT INTO log.user SELECT 2,NULL,NULL,"2015-10-28 13:52:44.785947"

有没有办法将",," 转换为",NULL,"

谢谢

-昆汀

【问题讨论】:

【参考方案1】:

首先我必须说,在我看来,使用 PostgreSQL 系统表(如information_schema)是这种用例的正确方法。特别是您必须编写一次:您创建函数prod.log_data() 并完成。此外,在这种情况下使用OLD(就像*)可能会很危险,因为没有指定元素顺序。

但是

要回答您的确切问题,我知道的唯一方法是对OLD 进行一些操作。只需观察您通过连接... ' SELECT ' || OLDOLD 转换为text。默认转换会创建丑陋的双逗号。因此,接下来您可以使用该文本。最后我建议:

DECLARE
     tmp TEXT
...
BEGIN
...
     /*to make OLD -> text like (2,,3,4,,)*/
     SELECT '' || OLD INTO tmp; /*step 1*/

     /*take care of commas at the begining and end: '(,' ',)'*/
     tmp := replace(replace(tmp, '(,', '(NULL,'), ',)', ',NULL)'); /*step 2*/

     /* replace rest of commas to commas with NULL between them */
     SELECT array_to_string(string_to_array(tmp, ',', ''), ',', 'NULL') INTO tmp; /*step 3*/

     /* Now we can do EXECUTE*/
     EXECUTE 'INSERT INTO log.' || TG_TABLE_NAME || ' SELECT ' || tmp;

当然你可以一步完成1-3步

SELECT array_to_string(string_to_array(replace(replace('' || NEW, '(,', '(NULL,'), ',)', ',NULL)'), ',', ''), ',', 'NULL') INTO tmp;

在我看来,这种方法并没有比使用 information_schema 更好,但这是你的决定。

【讨论】:

非常感谢!感谢您的意见,我将在大数据库上进行一些测试,看看 information_schema 是否对我的用例来说太慢了。 其实这是为了性能。但是你能告诉我postgres什么时候触发吗?我想知道 postgre 是否在触发触发器 after INSERT 之前返回到 php 我不是 100% 肯定,但我怀疑它是。 PostgreSQL 中的每个触发器(无论是AFTER 还是BEFORE)都在与主查询相同的事务中执行。这意味着在触发器结束之前执行不会结束。请注意,AFTER 触发器中未处理的异常会使整个事务回滚。因此,如果 php 认为即使 db 服务器本身还没有新的查询成功,也可能会出现问题。

以上是关于Postgresql 创建日志模式的主要内容,如果未能解决你的问题,请参考以下文章

C/C++为啥日志模块要设计成单例模式的?有啥好处?

防火墙(ASA)高级配置之URL过滤日志管理透明模式

尝试通过 liquibase、postgresql、spring boot 创建新数据库。结果->“数据库更改日志”不存在

设计模式:命令模式——命令模式扩展之日志请求

SpringBoot自定义注解+异步+观察者模式实现业务日志保存

完整模式下SQL日志收缩