在PostgreSQL中,是在事务完成之前(之内)还是之后执行的DEFERRED触发器?
DEFERRABLE
NOT DEFERRABLE
这可以控制是否可以延迟约束。约束 不可延迟的将在每次之后立即检查 命令。可以推迟检查可延迟的约束 直到交易结束(使用
SET CONSTRAINTS
命令)。
它没有指定它是否仍在交易内或外。我个人的经验说它在交易中,我需要它在外面!
在事务中是否执行了DEFERRED
(或INITIALLY DEFERRED
)触发器?如果是,我怎么能将他们的执行推迟到交易完成的时间?
为了给你一个提示,我正在使用pg_notify
和RabbitMQ(PostgreSQL LISTEN Exchange)来发送消息。我在外部应用程序中处理此类消息。现在我有一个触发器,它通过在消息中包含记录的id来通知外部应用程序新插入的记录。但是,以一种非确定性的方式,偶尔,当我尝试通过其手头的id选择记录时,无法找到记录。那是因为事务尚未完成,并且记录实际上没有添加到表中。如果我只能在事务完成后推迟执行触发器,那么一切都会成功。
为了获得更好的答案,让我更接近现实世界来解释情况。实际情况比我之前解释的要复杂一些。 source code can be found here如果有人感兴趣的话。由于我不打算深入研究的原因,我必须从另一个数据库发送通知,以便实际发送通知,如:
PERFORM * FROM dblink('hq','SELECT pg_notify(''' || channel || ''', ''' || payload || ''')');
我确信这会让整个情况变得更加复杂。
答案 0 :(得分:11)
触发器(包括所有类型的延迟触发器)在内部触发。
但这不是问题所在,因为无论如何都会在 交易之间传递 。
NOTIFY
以某些重要方式与SQL事务交互。 首先,如果在事务内执行NOTIFY
,则通知 除非交易发生,否则不会发送事件 承诺。这是合适的,因为如果交易中止, 其中的所有命令都没有效果,包括NOTIFY
。但 如果人们期待通知事件,那可能会令人不安 立即交付。其次,如果听力会话收到了 通知信号在交易内,通知 事件将不会发送到其连接的客户端,直到之后 交易完成(已提交或已中止)。再次, 推理是,如果通知是在一个 后来中止的事务,人们希望通知 以某种方式撤消 - 但服务器不能"收回"通知 一旦它发送给客户端。 所以通知事件只是 在交易之间交付。这就是结果 使用NOTIFY
进行实时信令的应用程序应该尽量保持 他们的交易很短。
大胆强调我的。
pg_notify()
只是SQL NOTIFY
命令的一个方便的包装函数。
如果在收到通知后找不到某些行,则必须有其他原因!去找吧。可能的候选人:
无论哪种方式,如手册所示,保留发送简短通知的交易是个好主意。
dblink
至于你以后的补充:
PERFORM * FROM dblink('hq','SELECT pg_notify(''' || channel || ''', ''' || payload || ''')');
...应该用format()
重写,以简化并使语法安全:
PRERFORM dblink('hq', format('NOTIFY %I, %L', channel, payload));
dblink 是一个游戏规则改变者,因为它在另一个数据库中打开一个单独的事务。这有时用于伪造 自治交易 。
dblink()
等待远程命令完成。所以远程事务很可能首先提交。 The manual:
该函数返回查询生成的行。
如果您可以从同一交易发送通知,那将是清洁解决方案。
如果必须从其他交易发送通知,则dblink_send_query()
会有解决方法:
dblink_send_query
发送一个异步执行的查询,即不立即等待结果。
DO -- or plpgsql function
$$
BEGIN
-- do stuff
PERFORM dblink_connect ('hq', 'your_connstr_or_foreign_server_here');
PERFORM dblink_send_query('con1', format('SELECT pg_sleep(3); NOTIFY %I, %L ', 'Channel', 'payload'));
PERFORM dblink_disconnect('con1');
END
$$;
如果您在事务结束之前执行此操作,则本地事务将提前3秒(pg_sleep(3)
)开始提交。选择适当的秒数。
这种方法存在固有的不确定性,因为如果出现任何问题,您不会收到任何错误消息。对于安全解决方案,您需要不同的设计。在成功发送命令之后,它仍然失败的可能性非常小。错过成功通知的可能性似乎多更高,但已经内置到您当前的解决方案中。
更安全的替代方法是写入队列表并像@Bohemian's answer中讨论的那样轮询它。这个相关的答案演示了如何安全地进行调查:
答案 1 :(得分:7)
我将此作为答案发布,假设您尝试解决的实际问题是在事务完成之后推迟执行外部流程(而不是使用触发器尝试解决的XY“问题”)功夫)。
让数据库告诉应用程序执行某些操作是一种破碎的模式。它被打破了因为:
相比之下,使用数据库作为持久队列,并让应用程序轮询它以便工作,并在工作完成时将工作从队列中取出,没有上述问题。
有很多方法可以实现这一目标。我更喜欢的是让一些进程(通常在插入,更新和删除时触发)将数据放入“队列”表中。让另一个进程轮询该工作表,并在工作完成时从表中删除。
它还增加了一些其他好处:
由数据库表支持的队列是在商业级基于队列的平台中实现持久队列的基础,因此该模式经过了充分测试,使用和理解。
让数据库做它最擅长的事情,并且它唯一做得很好:管理数据。不要试图将数据库服务器变成app服务器。