Postgresql 如何通过访问postgres中的数据正确模拟语句级触发器

Postgresql 如何通过访问postgres中的数据正确模拟语句级触发器,postgresql,postgresql-9.1,Postgresql,Postgresql 9.1,我正在使用PostgreSQL作为我工作项目的数据库。我们在相当多的地方使用触发器来维护计算列,或者基本上充当物化视图的表 当简单地利用行级触发器来保持所有这些同步时,所有这些都工作得很好。但是,当我们编写脚本定期将客户数据导入数据库时,我们遇到了性能问题或单个事务中的锁数问题 为了缓解这种情况,我想创建一个语句级触发器,可以访问修改的行(插入、更新或删除)。然而,由于这是不可能的,我反而在语句级触发器之前创建了一个,该触发器将创建一个临时表。然后是一个AFTER行级触发器,将更改的数据插入临时

我正在使用PostgreSQL作为我工作项目的数据库。我们在相当多的地方使用触发器来维护计算列,或者基本上充当物化视图的表

当简单地利用行级触发器来保持所有这些同步时,所有这些都工作得很好。但是,当我们编写脚本定期将客户数据导入数据库时,我们遇到了性能问题或单个事务中的锁数问题

为了缓解这种情况,我想创建一个语句级触发器,可以访问修改的行(插入、更新或删除)。然而,由于这是不可能的,我反而在语句级触发器之前创建了一个
,该触发器将创建一个临时表。然后是一个
AFTER
行级触发器,将更改的数据插入临时表。最后是一个
AFTER
语句级触发器,它将读取更改并执行必要的更新,然后删除临时表

所有这些工作都很好,假设在触发器中,没有人会再次重新触发相同的流(因为临时表已经存在)

然而,我后来了解到,当使用外键约束时,在DELETE SET NULL
上使用
,它只是通过一个系统触发器实现的,该系统触发器将列设置为
NULL
。这当然不是一个问题,除了这样一个事实:当您在一个表上有几个像这样的外键约束时,所有这些约束都引用同一个表(让我们称之为
文件
)。从
文件
表中删除行时,所有这些系统级触发器将同时触发
ON DELETE SET NULL
子句,即并行触发。这对我来说是个严重的问题

我将如何着手实施这样的事情?下面是一个简短的SQL脚本来说明问题:

CREATE TABLE files (
    id serial PRIMARY KEY,
    "name" TEXT NOT NULL
);

CREATE TABLE profiles (
    id serial PRIMARY KEY,
    NAME TEXT NOT NULL,
    cv_file_id INT REFERENCES files(id) ON DELETE SET NULL,
    photo_file_id INT REFERENCES files(id) ON DELETE SET NULL
);

CREATE TABLE profile_audit (
    profile_id INT NOT NULL,
    modified_at timestamptz NOT NULL
);

CREATE FUNCTION pre_stmt_create_temp_table()
RETURNS TRIGGER
AS $$
BEGIN
    CREATE TEMPORARY TABLE tmp_modified_profiles (
        id INT NOT NULL
    ) ON COMMIT DROP;
    RETURN NULL;
END;
$$ LANGUAGE 'plpgsql';

CREATE FUNCTION insert_modified_profile_to_temp_table()
RETURNS TRIGGER
AS $$
BEGIN
    INSERT INTO tmp_modified_profiles(id) VALUES (NEW.id);
    RETURN NULL;
END;
$$ LANGUAGE 'plpgsql';

CREATE FUNCTION post_stmt_insert_rows_and_drop_temp_table()
RETURNS TRIGGER
AS $$
BEGIN
    INSERT INTO profile_audit (id, modified_at)
    SELECT t.id, CURRENT_TIMESTAMP FROM tmp_modified_profiles t;

    DROP TABLE tmp_modified_profiles;

    RETURN NULL;
END;
$$ LANGUAGE 'plpgsql';

CREATE TRIGGER tr_create_working_table BEFORE UPDATE ON profiles FOR EACH STATEMENT EXECUTE PROCEDURE pre_stmt_create_temp_table();
CREATE TRIGGER tr_insert_row_to_working_table AFTER UPDATE ON profiles FOR EACH ROW EXECUTE PROCEDURE insert_modified_profile_to_temp_table();
CREATE TRIGGER tr_insert_modified_rows_and_drop_working_table AFTER UPDATE ON profiles FOR EACH STATEMENT EXECUTE PROCEDURE post_stmt_insert_rows_and_drop_temp_table();

INSERT INTO files ("name") VALUES ('photo.jpg'), ('my_cv.pdf');

INSERT INTO profiles ("name") VALUES ('John Doe');

DELETE FROM files WHERE "name" = 'photo.jpg';

这将是一个严重的黑客行为,但与此同时,在PostgreSQL 9.5发布之前,我会尝试使用
约束
触发器延迟直到事务结束。我不确定这是否有效,但可能值得一试。

您可以使用状态列跟踪语句级触发器的插入和更新

在插入或更新前的
行级触发器中:

    INSERT INTO status_table (table_name, op, id) VALUES (TG_TABLE_NAME, TG_OP, OLD.id);
现在,您可以在触发器之后使用语句级

    BEGIN
        DO FUNNY THINGS
         WHERE status = 'INSERT';

        -- reset the status
        UPDATE mytable
           SET status =  NULL
         WHERE status = 'INSERT';
    END;
但是,如果您还想处理删除,那么在行级触发器中需要类似以下内容:

    INSERT INTO status_table (table_name, op, id) VALUES (TG_TABLE_NAME, TG_OP, OLD.id);
然后,在您的语句级
触发器之后,您可以执行如下操作:

    BEGIN
        DO FUNNY THINGS
         WHERE id IN (SELECT id FROM status_table 
                       WHERE table_name = TG_TABLE_NAME AND op = TG_OP); -- just an example

        -- reset the status
        DELETE FROM status_table
         WHERE table_name = TG_TABLE_NAME AND op = TG_OP;
    END;

PostgreSQL 9.5中可能会出现对带有虚拟
旧表和
新表的语句级触发器的支持,其中对如何实现这一点进行了大量讨论。如果没有这样的东西,就没有什么好办法来改进现有的东西,否则并发性问题就相当成问题了。听起来不错。我看路线图已经有一段时间了。我已经注意到它已经在列表中了,但是我对这个功能的实现并没有太大的希望,因为我找不到一个列表来说明它计划何时完成。现在我只需要弄清楚在那之前该做什么…你说当从文件表中删除一行时,所有这些处理ON DELETE SET NULL子句的系统级触发器都同时触发,这是并行的。这对我来说是个严重的问题。那不是真的。他们一个接一个连续开火。这到底给你带来了什么问题?演出其他?问题是,当为第二个外键列触发时,
之前的
语句级触发器无法创建临时表。使用发布的脚本,您应该可以看到问题。我试图通过在
语句触发器之前的
中增加计数器,然后在
语句触发器之后的
中打印计数器来验证它们是否并行触发。所有打印的值都是一样的。啊,好的。它们不会并行发射,但它们看不到彼此的影响,这似乎是给你带来麻烦的原因。不幸的是,我发现临时表和触发器的组合充其量是脆弱的,在大多数情况下是不可行的……但即使它起作用,缺点是在提交事务之前,您的物化数据将不一致。这在某些情况下可能是可以接受的,但通常是脆弱的。