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