依赖外键约束来避免删除

Aka*_*ash 1 postgresql

在 PostgreSQL 中,我有 2 个具有父/父关系的表。如果没有孩子的记录与之关联,我希望删除父亲记录(使用后触发器)

目前,我一直在做类似的事情

  1. 获取记录数(与当前子项具有相同的父 ID)
  2. 如果计数为1,则删除当前子记录并删除其父记录
  3. 如果计数大于 1,则删除子记录(因为更多子记录依赖于父记录)

我想知道我是否直接触发删除语句而不获取父记录的计数,例如

  1. 删除子记录
  2. 删除父记录(如果有依赖会抛出错误)

在这种情况下,如果任何子项与父项相关联(通过外键),它将抛出错误并且不会被删除

第二种方法是否正确,要完成此操作,是否在 PostgreSQL 中记录了此类错误(外键约束依赖项)

Cra*_*ger 5

似乎您的意图是在删除最后一个子项时删除父记录。

如果是这样,虽然您可以发出无条件DELETE删除父项并让与现有子项的外键关系阻止它,但这可能不是最佳策略。要使其完全工作,您必须在DELETE与子事务不同的事务(或子事务)中执行父事务DELETE,否则当DELETE父事务失败并且事务中止时,整个事务的工作将被撤消。

在一个完全独立的事务中做这件事不是很安全,因为它会留下一个没有孩子的父母的窗口——如果你重复使用密钥,这是完全不可接受的。

删除子事务然后在子事务中使用SAVEPOINT和删除父项ROLLBACK TO SAVEPOINT是可以的,尽管很尴尬。但是,您需要考虑当两个并发事务删除同一父级的最后两个子级时会发生什么;你会得到一个孤儿父记录,因为每个人都会看到另一个正在删除的“剩余”孩子。为了防止这种情况,您需要SELECT ... FOR UPDATE在删除孩子之前联系父母。

BEGIN;
SELECT 1 FROM parent WHERE parent_id = 1 FOR UPDATE;
DELETE FROM child WHERE child_id = 11 and parent_id = 1;
SAVEPOINT delete_parent;
DELETE FROM parent WHERE parent_id = 1;
-- In the application see if the DELETE was successful. If it was, COMMIT.
-- if it failed, run:
ROLLBACK TO SAVEPOINT delete_parent;
-- before committing the deletion of the child.
COMMIT;
Run Code Online (Sandbox Code Playgroud)

或者,因为您正在强制从父级中删除子级的事务连续发生,所以您可以将SAVEPOINT下面的所有内容替换为:

DELETE FROM parent 
WHERE parent_id = 1 AND NOT EXISTS (
  SELECT 1 FROM child c WHERE c.parent_id = parent.parent_id
);
Run Code Online (Sandbox Code Playgroud)

另一种策略是使用SERIALIZABLE隔离事务乐观地删除父级而不是使用初始SELECT ... FOR UPDATE. 如果其他人创建了一个新子项或做了其他会发生冲突的事情,PostgreSQL 9.1 或更高版本中的可序列化隔离将中止冲突事务之一。您的应用程序必须准备好处理错误并重新运行中止的事务。