Postgresql“反向”外键“约束”?

时间:2015-09-04 15:51:32

标签: postgresql

两张桌子:

Table A
id bigint;    -- Table A primary key

Table B
id   bigint;  -- Table B primary key
a_id bigint;  -- Table A foreign key

Table A插入时,我想确保Table B包含Table A的条目,如果没有则回滚。

在PostgreSQL中,AFTER INSERT触发器是最好/唯一的方法吗?

这种关系有名字吗?

感谢。

1 个答案:

答案 0 :(得分:2)

您想要的是一对多的强制关系,即对于m:n关系,n方可能不为零。

使用触发器

有两种方法可以做到这一点,但主要方法是在AB上定义触发器:

  • AFTER INSERT OR UPDATE上的A触发器会检查以确保存在B B.a_id = NEW.id行;

  • AFTER UPDATE OR DELETE上的B触发器会检查以确保(DELETEUPDATE更改a_id)至少还有一个剩余的B具有相同的a_id,因此不会违反约束。

他们可以成为AFTER触发器,并且实际上是必要的,以便允许某些类型的行交换发生。它们应该被创建为约束触发器。

需要DEFERRABLE

应该清楚的是,上述内容不会完全按照书面形式起作用,因为B.a_id上可能存在FK约束来强制存在相应的A.id。因此,在INSERT之前,您无法B A,并且在A之前无法插入B。那你做什么?

使触发器和/或外键约束可以推迟。

(你可以使用可写的CTE在同一个语句中插入AB,但是......呃。不要。)

竞争条件怎么样?

想象一下,你有AB个。{两个并发事务都将从B中删除。

每个人都看到有另一个B来满足约束。所以每个都允许DELETE继续。哎呦。两者都提交,现在您为B提供了A个零。你做什么的?

选项:

  • SERIALIZABLE隔离。在这种情况下,一个或两个事务将因序列化失败而失败。应用程序必须准备好捕获错误并重试或通知用户他们无法删除该行,因为它是唯一剩下的行。一个写得很好的应用总是准备好重试由于瞬态死锁,服务器重启,连接问题而失败的查询,所以这通常是最好的选择。请记住,必须重做整个交易

  • 让每个SELECT 1 FROM A WHERE A.id = 'the-B.a_id' FOR UPDATE对他们正在修改的A进行行锁定。因此,总是会等待另一个提交或回滚。这很好用。您通常可以将SELECT ... FOR UPDATE的{​​{1}}锁定在A的触发器内,但前提是您永远不会将BFOR SHARE锁定在来自FOR UPDATE的其他行,否则您可能会遇到死锁。在这种情况下,您必须使用事务重试来处理死锁中止。我们使用A锁定,以便FOR UPDATE中的两个并发DELETE被序列化。

出于类似的原因,B上的触发器应对A存在的FOR SHARE行进行B锁定,以确保AFTER INSERT OR UPDATE存在时检查{存在{1}}行。否则,并发事务可以B这些行或DELETE具有不同的UPDATE。这里有一个B.a_id锁就足够了,因为你不必阻止对该A行的并发插入/更新,自动对该行进行锁定。

测试。手。

你需要进行大量的竞争条件测试。

PostgreSQL的源代码树中的隔离测试工具对此非常有用,因为可以简单地编写尝试使用不同进程/线程同时进行各种更改组合的程序,然后通过健全性检查结果。端。

有更简单的方法吗?

您可以将FOR SHARE列添加到b_id,并将A NOT NULL引用标记为FOREIGN KEY的{​​{1}}。当您想要更改A / B关系时B并进行更改。

这基本上将一个DEFERRABLE INITIALLY IMMEDIATE标记为SET CONSTRAINTS DEFERRED的“主要”,确保其中一个已经存在。