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

时间:2015-01-08 10:10:14

标签: postgresql postgresql-9.1

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

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

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

所有这一切都运行正常,假设在触发器内,没有人会再次重新触发相同的流(因为临时表将会存在)。

然而,我了解到,当使用ON DELETE SET NULL的外键约束时,只需使用将列设置为NULL的系统触发器即可实现。这当然不是问题,除了当你在一个表上有这样的几个外键约束时,所有引用相同的表(让我们只调用这个files)。从files表中删除行时,所有这些处理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';

2 个答案:

答案 0 :(得分:1)

这将是一个严重的黑客,但同时,在PostgreSQL 9.5退出之前,我会尝试在事务结束时使用CONSTRAINT触发器延迟。我不确定这会有效,但可能值得尝试。

答案 1 :(得分:0)

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

BEFORE INSERT OR UPDATE 行级触发器中:

    SET NEW.status = TG_OP;

现在您可以使用语句级 AFTER触发器:

    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);

然后,在您的语句级AFTER触发器中,您可以像:

    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;