Kof*_*fiB 9 postgresql stored-procedures transactions plpgsql task-queue
在 Postgres 12 数据库中,我在一个函数中有多个查询(SELECT、UPDATE、 ...),总共需要大约 20 分钟才能完成。我在顶部有一个检查,它执行UPDATEifstatus未运行:
create or replace function aaa.fnc_work() returns varchar as
$body$
begin
if (select count(*) from aaa.monitor where id='invoicing' and status='running')=0 then
return 'running';
else
update aaa.monitor set status='running' where id='invoicing';
end if;
--- rest of code ---
--finally
update aaa.monitor set status='idle' where id='invoicing';
return '';
exception when others then
return SQLERRM::varchar;
end
$body$
language plpgsql;
Run Code Online (Sandbox Code Playgroud)
这个想法是为了防止其他用户执行直到--- rest of code ---空闲status。
然而,其他人(调用相同的函数)似乎看不到更新的状态,他们也继续并开始执行--- rest of code ---。如何在以下情况后强制提交:
更新 aaa.monitor 设置 status='running'
where id='invoicing';
这样所有其他用户会话都可以看到更新status并相应退出。
我需要交易吗?
Erw*_*ter 16
继续阅读。我把最好的留到最后。
\nPROCEDUREPostgresFUNCTION始终是原子的(在单个事务包装器内运行)并且无法处理事务。所以COMMIT是不允许的。您可以使用技巧来dblink解决这个问题。看:
但对于像这样的嵌套事务,请考虑使用 aPROCEDURE代替。随Postgres 11引入。您可以在那里管理交易:
CREATE OR REPLACE PROCEDURE aaa.proc_work(_id text, INOUT _result text = NULL)\n LANGUAGE plpgsql AS\n$proc$\nBEGIN\n -- optionally assert steering row exists\n PERFORM FROM aaa.monitor WHERE id = _id FOR KEY SHARE SKIP LOCKED;\n\n IF NOT FOUND THEN \n RAISE EXCEPTION \'monitor.id = % not found or blocked!\', quote_literal(_id);\n END IF;\n\n -- try UPDATE\n UPDATE aaa.monitor\n SET status = \'running\'\n WHERE id = _id -- assuming valid _id\n AND status <> \'running\'; -- assuming "status" is NOT NULL\n\n IF NOT FOUND THEN\n _result := \'running\'; RETURN; -- this is how you return with INOUT params\n END IF;\n\n COMMIT; -- HERE !!!\n\n BEGIN -- start new code block\n\n ----- code for big work HERE -----\n -- PERFORM 1/0; -- debug: test exception?\n -- PERFORM pg_sleep(5); -- debug: test concurrency?\n\n _result := \'\';\n\n -- also catching QUERY_CANCELED and ASSERT_FAILURE\n -- is a radical step to try and release \'running\' rows no matter what\n EXCEPTION WHEN OTHERS OR QUERY_CANCELED OR ASSERT_FAILURE THEN\n -- ROLLBACK; -- roll back (unfinished?) big work\n _result := SQLERRM;\n END; -- end of nested block\n\n UPDATE aaa.monitor -- final reset\n SET status = \'idle\'\n WHERE id = _id\n AND status <> \'idle\'; -- only if needed\nEND\n$proc$;\nRun Code Online (Sandbox Code Playgroud)\n致电(重要!):
\nCALL aaa.proc_work(\'invoicing\'); -- stand-alone call!\nRun Code Online (Sandbox Code Playgroud)\n添加COMMIT在UPDATE. 之后,并发事务就可以看到更新的行。
但没有额外的BEGIN或START TRANSACTION。手册:
\n\n在命令调用的过程
\nCALL以及匿名代码块(DO命令)中,可以使用命令COMMIT和结束事务ROLLBACK。使用这些命令结束事务后,\n会自动启动新事务,因此\n没有单独的START TRANSACTION命令。(请注意,BEGIN和\nEND在 PL/pgSQL 中具有不同的含义。)
我们需要一个单独的 PL/pgSQL代码块,因为您有一个自定义异常处理程序,并且(引用手册):
\n\n\n事务无法在带有异常处理程序的块内结束。
\n
(但我们可以在处理程序中COMMIT/ 。)ROLLBACKEXCEPTION
您不能在外部事务内部调用此过程,也不能与任何其他 DML 语句一起调用,否则会强制使用外部事务包装器。必须是独立的CALL。看:
注意最后的UPDATE aaa.monitor SET status = \'idle\' WHERE .... 否则(已提交!)status将在异常后无限期地保持“运行”。
关于从过程返回值:
\n\n我添加DEFAULT NULL了参数INOUT,因此您不必在调用时提供参数。
UPDATE直接地。如果该行正在“运行”,则不会发生更新。(这也修复了逻辑:当没有找到任何行时,您的IF表达式似乎向后返回“正在运行” 。似乎您想要相反的情况。)status=\'running\'
我添加了一个(可选!)断言以确保表中的行aaa.monitor存在。添加FOR KEY SHARE锁还可以消除断言和后续之间的竞争条件的微小时间窗口UPDATE。锁与删除或更新 PK 列冲突,但与更新status. 所以在正常操作中永远不会引发异常!手册:
\n\n目前,针对该情况考虑的列集
\nUPDATE是\n那些具有可在外键中使用的唯一索引的列(因此不考虑部分索引和表达式索引),\n但这在将来可能会发生变化。
SKIP LOCK在锁冲突的情况下不等待。添加的异常永远不应该发生。只是展示一个无懈可击的概念证明。
您的更新显示了 25 行aaa.monitor,因此我添加了参数_id。
上述内容对于保留更多信息以供全世界查看可能是有意义的。对于队列操作,还有更有效的解决方案。而是使用锁,其他人立即“可见”。那么你不需要一个嵌套事务来开始,一个简单的事务FUNCTION就可以了:
CALL aaa.proc_work(\'invoicing\'); -- stand-alone call!\nRun Code Online (Sandbox Code Playgroud)\n称呼:
\nSELECT aaa.fnc_work(\'invoicing\');\nRun Code Online (Sandbox Code Playgroud)\n该调用可以以任何您想要的方式嵌套。只要一项事务正在完成这项重大工作,其他事务就不会启动。
\n同样,可选的断言会取出一个FOR KEY SHARE锁来消除竞争条件的时间窗口,并且添加的异常在正常操作中永远不应该发生。
status我们根本不需要这个列。行锁本身就是看门人。SELECT因此中的空列表PERFORM FROM aaa.monitor ...。附带好处:这也不会通过来回更新行而产生死元组。如果由于其他原因您仍然需要更新status,那么您又回到了上一章的可见性问题。您可以将两者结合起来...
关于PERFORM:
关于行锁:
\n\n| 归档时间: |
|
| 查看次数: |
13979 次 |
| 最近记录: |