两张桌子:
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
触发器是最好/唯一的方法吗?
这种关系有名字吗?
感谢。
答案 0 :(得分:2)
您想要的是一对多的强制关系,即对于m:n关系,n方可能不为零。
有两种方法可以做到这一点,但主要方法是在A
和B
上定义触发器:
AFTER INSERT OR UPDATE
上的A
触发器会检查以确保存在B
B.a_id = NEW.id
行;
AFTER UPDATE OR DELETE
上的B
触发器会检查以确保(DELETE
或UPDATE
更改a_id
)至少还有一个剩余的B
具有相同的a_id
,因此不会违反约束。
他们可以成为AFTER
触发器,并且实际上是必要的,以便允许某些类型的行交换发生。它们应该被创建为约束触发器。
DEFERRABLE
应该清楚的是,上述内容不会完全按照书面形式起作用,因为B.a_id
上可能存在FK约束来强制存在相应的A.id
。因此,在INSERT
之前,您无法B
A
,并且在A
之前无法插入B
。那你做什么?
使触发器和/或外键约束可以推迟。
(你可以使用可写的CTE在同一个语句中插入A
和B
,但是......呃。不要。)
想象一下,你有A
个B
个。{两个并发事务都将从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
的触发器内,但前提是您永远不会将B
或FOR 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
的“主要”,确保其中一个已经存在。