我正在使用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';
答案 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;