postgres中级联删除的顺序
Order of cascaded deletes in postgres
我遇到了一个问题,这让我怀疑 Postgres 仅从依赖 tables (ON DELETE CASCADE) 中删除行在它已经删除了原始行。
我有这些 table:
CREATE TABLE IF NOT EXISTS function (
id UUID PRIMARY KEY,
level VARCHAR(4) NOT NULL CHECK (level IN ('ORG', 'DEP', 'GRP', 'SESS')),
name VARCHAR(64) NOT NULL UNIQUE,
type VARCHAR(15) NOT NULL CHECK (type IN ('SYSTEM', 'SYS-AUTO-ASSIGN', 'CUSTOM'))
);
CREATE TABLE IF NOT EXISTS function_inclusion (
super_function UUID REFERENCES function (id) ON DELETE CASCADE,
sub_function UUID REFERENCES function (id) ON DELETE CASCADE,
UNIQUE (super_function, sub_function)
);
我在 function_inclusion table:
上创建了一个触发器(删除前)
CREATE OR REPLACE FUNCTION trg_function_inclusion_del_bef()
RETURNS trigger AS
$func$
DECLARE
function_type VARCHAR(15);
BEGIN
SELECT type INTO function_type FROM function WHERE id = OLD.super_function;
RAISE NOTICE 'function_type: %', function_type;
-- do stuff based on the function_type of the super_function
CASE
WHEN function_type = 'SYSTEM' OR function_type = 'SYS-AUTO-ASSIGN' THEN
-- (do stuff)
WHEN function_type = 'CUSTOM' THEN
-- (do stuff)
ELSE RAISE EXCEPTION 'The function % doesn''t have a correct type', OLD.super_function;
END CASE;
RETURN OLD;
END
$func$
LANGUAGE plpgsql;
DROP TRIGGER IF EXISTS function_inclusion_delete_before ON function_inclusion CASCADE;
CREATE TRIGGER function_inclusion_delete_before
BEFORE DELETE ON function_inclusion
FOR EACH ROW
EXECUTE PROCEDURE trg_function_inclusion_del_bef();
假设我有 2 个函数和一个 function_inclusion:
INSERT INTO function (id, level, name, type)
VALUES ('abcf3dbc-9433-4b73-b9c1-f00745dc1175', 'DEP', 'custom-function-1', 'CUSTOM');
INSERT INTO function (id, level, name, type)
VALUES ('360bde13-7953-49ed-a923-793b2d828d7e', 'DEP', 'custom-function-2', 'CUSTOM');
INSERT INTO function_inclusion (super_function, sub_function)
VALUES ('abcf3dbc-9433-4b73-b9c1-f00745dc1175', '360bde13-7953-49ed-a923-793b2d828d7e');
当我删除 super_function:
DELETE FROM function WHERE id = 'abcf3dbc-9433-4b73-b9c1-f00745dc1175';
我收到这个错误:
NOTICE: function_type: <NULL>
Query 1 ERROR: ERROR: The function abcf3dbc-9433-4b73-b9c1-f00745dc1175 doesn't have a correct type
CONTEXT: PL/pgSQL function trg_function_inclusion_del_bef() line 13 at RAISE
SQL statement "DELETE FROM ONLY "public"."function_inclusion" WHERE OPERATOR(pg_catalog.=) "super_function""
所以好像这个函数已经被删除了,我无法再从 function_inclusion 上的触发器访问它。
我试图找到有关 'ON DELETE CASCADE' 的更多信息,但我到处都看到它只说 'the referencing rows are automatically deleted',没有提到首先删除的是引用行还是被引用行。
postgres 在删除从属(引用)table 中的行之前是否先删除原始(引用)行?
如果是这样,我怎样才能实现同样的事情而不必在我的 function_inclusion table 中存储冗余数据?
为了防止其他人遇到同样的问题,我发布了我找到的解决方案。
问题是 - 正如我所怀疑的那样 - postgres 首先删除请求的行本身,然后再从依赖 table 中删除行(已设置 'ON DELETE CASCADE')。
我找到的解决方案有点复杂,在 'function' table 上实现了软删除(参见 Cascading Soft Delete)
- 首先我在“函数”中添加了一个额外的字段deleted_at table:
CREATE TABLE IF NOT EXISTS function (
id UUID PRIMARY KEY,
level VARCHAR(4) NOT NULL CHECK (level IN ('ORG', 'DEP', 'GRP', 'SESS')),
name VARCHAR(64) NOT NULL UNIQUE,
type VARCHAR(15) NOT NULL CHECK (type IN ('SYSTEM', 'SYS-AUTO-ASSIGN', 'CUSTOM')),
deleted_at TIMESTAMPTZ DEFAULT NULL
);
这个 table 包含所有“功能”行,包括已经(软)删除的行。
从现在开始,我们将需要使用“SELECT FROM ONLY 函数”来 select 未删除的行。
- 然后我创建了一个继承的table "function_deleted":
CREATE TABLE IF NOT EXISTS function_deleted () INHERITS(function);
插入此 table 的行也将在 table“函数”中找到。
要查找已删除的行,我们需要使用“SELECT FROM function_deleted”。
- 然后我创建了一个通用的触发函数,用于从任何 table:
中软删除一行
CREATE OR REPLACE FUNCTION tr_soft_delete_row()
RETURNS TRIGGER AS
$$
BEGIN
IF (TG_OP = 'UPDATE' AND NEW.deleted_at IS NOT NULL) THEN
EXECUTE format('DELETE FROM %I.%I WHERE id = ', TG_TABLE_SCHEMA, TG_TABLE_NAME) USING OLD.id;
RETURN OLD;
END IF;
IF (TG_OP = 'DELETE') THEN
IF (OLD.deleted_at IS NULL) THEN
OLD.deleted_at := timenow();
END IF;
EXECUTE format('INSERT INTO %I.%I SELECT .*', TG_TABLE_SCHEMA, TG_TABLE_NAME || '_deleted') USING OLD;
END IF;
RETURN OLD;
END;
$$
LANGUAGE plpgsql;
当在删除行时从触发器调用时,该函数设置 deleted_at 字段并将该行插入到“(table)_deleted”table.
在 deleted_at 字段更新时从触发器调用时,此函数会删除该行(这将自动成为软删除)
- 然后我创建了一个触发器来调用这个软删除函数:
CREATE TRIGGER _soft_delete_function
AFTER
UPDATE OF deleted_at
OR DELETE
ON function
FOR EACH ROW
EXECUTE PROCEDURE tr_soft_delete_row();
(触发器名称前面的下划线确保触发器将在任何其他触发器之前被调用)
- 现在我像以前一样创建“function_inclusion”table:
CREATE TABLE IF NOT EXISTS function_inclusion (
super_function UUID REFERENCES function (id) ON DELETE CASCADE,
sub_function UUID REFERENCES function (id) ON DELETE CASCADE,
UNIQUE (super_function, sub_function)
);
- 以及这个table的触发函数和触发器:
CREATE OR REPLACE FUNCTION trg_function_inclusion_del_aft()
RETURNS trigger AS
$func$
DECLARE
function_type VARCHAR(15);
BEGIN
SELECT type INTO function_type FROM ONLY function WHERE id = OLD.super_function;
-- if the function doesn't exist then it's because it was just deleted
--> find it in table "function_deleted"
IF NOT FOUND THEN
SELECT type INTO function_type FROM function_deleted WHERE id = OLD.super_function
ORDER BY deleted_at DESC NULLS LAST LIMIT 1;
END IF;
RAISE NOTICE 'function_type: %', function_type;
-- do stuff based on the function_type of the super_function
CASE
WHEN function_type = 'SYSTEM' OR function_type = 'SYS-AUTO-ASSIGN' THEN
-- (do stuff)
WHEN function_type = 'CUSTOM' THEN
-- (do stuff)
ELSE RAISE EXCEPTION 'The function % doesn''t have a correct type', OLD.super_function;
END CASE;
RETURN OLD;
END
$func$
LANGUAGE plpgsql;
CREATE TRIGGER function_inclusion_delete_after
AFTER DELETE ON function_inclusion
FOR EACH ROW
EXECUTE PROCEDURE trg_function_inclusion_del_aft();
- 现在,当我创建函数和函数包含时:
INSERT INTO function (id, level, name, type)
VALUES ('abcf3dbc-9433-4b73-b9c1-f00745dc1175', 'DEP', 'custom-function-1', 'CUSTOM');
INSERT INTO function (id, level, name, type)
VALUES ('360bde13-7953-49ed-a923-793b2d828d7e', 'DEP', 'custom-function-2', 'CUSTOM');
INSERT INTO function_inclusion (super_function, sub_function)
VALUES ('abcf3dbc-9433-4b73-b9c1-f00745dc1175', '360bde13-7953-49ed-a923-793b2d828d7e');
然后删除其中一个函数:
DELETE FROM ONLY function WHERE id = 'abcf3dbc-9433-4b73-b9c1-f00745dc1175';
我现在没有收到错误,而且我在引发的通知中得到了正确的信息(来自 function_inclusion 上的 AFTER DELETE 触发器):
NOTICE: function_type: CUSTOM
Query 1 OK: DELETE 1, 1 row affected
这种方法的一个缺点是,现在在我的 SQL 代码中的任何地方,我都需要记住使用“SELECT * FROM ONLY function”而不是“SELECT * FROM function” ".
我遇到了一个问题,这让我怀疑 Postgres 仅从依赖 tables (ON DELETE CASCADE) 中删除行在它已经删除了原始行。
我有这些 table:
CREATE TABLE IF NOT EXISTS function (
id UUID PRIMARY KEY,
level VARCHAR(4) NOT NULL CHECK (level IN ('ORG', 'DEP', 'GRP', 'SESS')),
name VARCHAR(64) NOT NULL UNIQUE,
type VARCHAR(15) NOT NULL CHECK (type IN ('SYSTEM', 'SYS-AUTO-ASSIGN', 'CUSTOM'))
);
CREATE TABLE IF NOT EXISTS function_inclusion (
super_function UUID REFERENCES function (id) ON DELETE CASCADE,
sub_function UUID REFERENCES function (id) ON DELETE CASCADE,
UNIQUE (super_function, sub_function)
);
我在 function_inclusion table:
上创建了一个触发器(删除前)CREATE OR REPLACE FUNCTION trg_function_inclusion_del_bef()
RETURNS trigger AS
$func$
DECLARE
function_type VARCHAR(15);
BEGIN
SELECT type INTO function_type FROM function WHERE id = OLD.super_function;
RAISE NOTICE 'function_type: %', function_type;
-- do stuff based on the function_type of the super_function
CASE
WHEN function_type = 'SYSTEM' OR function_type = 'SYS-AUTO-ASSIGN' THEN
-- (do stuff)
WHEN function_type = 'CUSTOM' THEN
-- (do stuff)
ELSE RAISE EXCEPTION 'The function % doesn''t have a correct type', OLD.super_function;
END CASE;
RETURN OLD;
END
$func$
LANGUAGE plpgsql;
DROP TRIGGER IF EXISTS function_inclusion_delete_before ON function_inclusion CASCADE;
CREATE TRIGGER function_inclusion_delete_before
BEFORE DELETE ON function_inclusion
FOR EACH ROW
EXECUTE PROCEDURE trg_function_inclusion_del_bef();
假设我有 2 个函数和一个 function_inclusion:
INSERT INTO function (id, level, name, type)
VALUES ('abcf3dbc-9433-4b73-b9c1-f00745dc1175', 'DEP', 'custom-function-1', 'CUSTOM');
INSERT INTO function (id, level, name, type)
VALUES ('360bde13-7953-49ed-a923-793b2d828d7e', 'DEP', 'custom-function-2', 'CUSTOM');
INSERT INTO function_inclusion (super_function, sub_function)
VALUES ('abcf3dbc-9433-4b73-b9c1-f00745dc1175', '360bde13-7953-49ed-a923-793b2d828d7e');
当我删除 super_function:
DELETE FROM function WHERE id = 'abcf3dbc-9433-4b73-b9c1-f00745dc1175';
我收到这个错误:
NOTICE: function_type: <NULL>
Query 1 ERROR: ERROR: The function abcf3dbc-9433-4b73-b9c1-f00745dc1175 doesn't have a correct type
CONTEXT: PL/pgSQL function trg_function_inclusion_del_bef() line 13 at RAISE
SQL statement "DELETE FROM ONLY "public"."function_inclusion" WHERE OPERATOR(pg_catalog.=) "super_function""
所以好像这个函数已经被删除了,我无法再从 function_inclusion 上的触发器访问它。
我试图找到有关 'ON DELETE CASCADE' 的更多信息,但我到处都看到它只说 'the referencing rows are automatically deleted',没有提到首先删除的是引用行还是被引用行。
postgres 在删除从属(引用)table 中的行之前是否先删除原始(引用)行? 如果是这样,我怎样才能实现同样的事情而不必在我的 function_inclusion table 中存储冗余数据?
为了防止其他人遇到同样的问题,我发布了我找到的解决方案。
问题是 - 正如我所怀疑的那样 - postgres 首先删除请求的行本身,然后再从依赖 table 中删除行(已设置 'ON DELETE CASCADE')。
我找到的解决方案有点复杂,在 'function' table 上实现了软删除(参见 Cascading Soft Delete)
- 首先我在“函数”中添加了一个额外的字段deleted_at table:
CREATE TABLE IF NOT EXISTS function (
id UUID PRIMARY KEY,
level VARCHAR(4) NOT NULL CHECK (level IN ('ORG', 'DEP', 'GRP', 'SESS')),
name VARCHAR(64) NOT NULL UNIQUE,
type VARCHAR(15) NOT NULL CHECK (type IN ('SYSTEM', 'SYS-AUTO-ASSIGN', 'CUSTOM')),
deleted_at TIMESTAMPTZ DEFAULT NULL
);
这个 table 包含所有“功能”行,包括已经(软)删除的行。 从现在开始,我们将需要使用“SELECT FROM ONLY 函数”来 select 未删除的行。
- 然后我创建了一个继承的table "function_deleted":
CREATE TABLE IF NOT EXISTS function_deleted () INHERITS(function);
插入此 table 的行也将在 table“函数”中找到。 要查找已删除的行,我们需要使用“SELECT FROM function_deleted”。
- 然后我创建了一个通用的触发函数,用于从任何 table: 中软删除一行
CREATE OR REPLACE FUNCTION tr_soft_delete_row()
RETURNS TRIGGER AS
$$
BEGIN
IF (TG_OP = 'UPDATE' AND NEW.deleted_at IS NOT NULL) THEN
EXECUTE format('DELETE FROM %I.%I WHERE id = ', TG_TABLE_SCHEMA, TG_TABLE_NAME) USING OLD.id;
RETURN OLD;
END IF;
IF (TG_OP = 'DELETE') THEN
IF (OLD.deleted_at IS NULL) THEN
OLD.deleted_at := timenow();
END IF;
EXECUTE format('INSERT INTO %I.%I SELECT .*', TG_TABLE_SCHEMA, TG_TABLE_NAME || '_deleted') USING OLD;
END IF;
RETURN OLD;
END;
$$
LANGUAGE plpgsql;
当在删除行时从触发器调用时,该函数设置 deleted_at 字段并将该行插入到“(table)_deleted”table.
在 deleted_at 字段更新时从触发器调用时,此函数会删除该行(这将自动成为软删除)
- 然后我创建了一个触发器来调用这个软删除函数:
CREATE TRIGGER _soft_delete_function
AFTER
UPDATE OF deleted_at
OR DELETE
ON function
FOR EACH ROW
EXECUTE PROCEDURE tr_soft_delete_row();
(触发器名称前面的下划线确保触发器将在任何其他触发器之前被调用)
- 现在我像以前一样创建“function_inclusion”table:
CREATE TABLE IF NOT EXISTS function_inclusion (
super_function UUID REFERENCES function (id) ON DELETE CASCADE,
sub_function UUID REFERENCES function (id) ON DELETE CASCADE,
UNIQUE (super_function, sub_function)
);
- 以及这个table的触发函数和触发器:
CREATE OR REPLACE FUNCTION trg_function_inclusion_del_aft()
RETURNS trigger AS
$func$
DECLARE
function_type VARCHAR(15);
BEGIN
SELECT type INTO function_type FROM ONLY function WHERE id = OLD.super_function;
-- if the function doesn't exist then it's because it was just deleted
--> find it in table "function_deleted"
IF NOT FOUND THEN
SELECT type INTO function_type FROM function_deleted WHERE id = OLD.super_function
ORDER BY deleted_at DESC NULLS LAST LIMIT 1;
END IF;
RAISE NOTICE 'function_type: %', function_type;
-- do stuff based on the function_type of the super_function
CASE
WHEN function_type = 'SYSTEM' OR function_type = 'SYS-AUTO-ASSIGN' THEN
-- (do stuff)
WHEN function_type = 'CUSTOM' THEN
-- (do stuff)
ELSE RAISE EXCEPTION 'The function % doesn''t have a correct type', OLD.super_function;
END CASE;
RETURN OLD;
END
$func$
LANGUAGE plpgsql;
CREATE TRIGGER function_inclusion_delete_after
AFTER DELETE ON function_inclusion
FOR EACH ROW
EXECUTE PROCEDURE trg_function_inclusion_del_aft();
- 现在,当我创建函数和函数包含时:
INSERT INTO function (id, level, name, type)
VALUES ('abcf3dbc-9433-4b73-b9c1-f00745dc1175', 'DEP', 'custom-function-1', 'CUSTOM');
INSERT INTO function (id, level, name, type)
VALUES ('360bde13-7953-49ed-a923-793b2d828d7e', 'DEP', 'custom-function-2', 'CUSTOM');
INSERT INTO function_inclusion (super_function, sub_function)
VALUES ('abcf3dbc-9433-4b73-b9c1-f00745dc1175', '360bde13-7953-49ed-a923-793b2d828d7e');
然后删除其中一个函数:
DELETE FROM ONLY function WHERE id = 'abcf3dbc-9433-4b73-b9c1-f00745dc1175';
我现在没有收到错误,而且我在引发的通知中得到了正确的信息(来自 function_inclusion 上的 AFTER DELETE 触发器):
NOTICE: function_type: CUSTOM
Query 1 OK: DELETE 1, 1 row affected
这种方法的一个缺点是,现在在我的 SQL 代码中的任何地方,我都需要记住使用“SELECT * FROM ONLY function”而不是“SELECT * FROM function” ".