我正在寻求澄清如何确保 plpgsql 函数中的原子事务,以及为数据库的这一特定更改设置隔离级别的位置。
在下面显示的 plpgsql 函数中,我想确保删除和插入都成功。当我尝试将它们包装在单个事务中时出现错误:
ERROR: cannot begin/end transactions in PL/pgSQL
如果另一个用户添加了针对情况的默认行为(“RAIN”、“NIGHT”、“45MPH”),那么在执行下面的函数期间会发生什么?after此函数删除了自定义行,但before它有一个有机会插入自定义行吗?是否存在包装插入和删除的隐式事务,以便如果另一个用户更改了此函数引用的任一行,则两者都会回滚?我可以为此函数设置隔离级别吗?
create function foo(v_weather varchar(10), v_timeofday varchar(10), v_speed varchar(10),
v_behavior varchar(10))
returns setof CUSTOMBEHAVIOR
as $body$
begin
-- run-time error if either of these lines is un-commented
-- start transaction ISOLATION LEVEL READ COMMITTED;
-- or, alternatively, set transaction ISOLATION LEVEL READ COMMITTED;
delete from CUSTOMBEHAVIOR
where weather = 'RAIN' and timeofday = 'NIGHT' and speed= '45MPH' ;
-- if there is no default behavior insert a custom behavior
if not exists
(select id from DEFAULTBEHAVIOR where a = 'RAIN' and b = 'NIGHT' and c= '45MPH') then
insert into CUSTOMBEHAVIOR
(weather, timeofday, speed, behavior)
values
(v_weather, v_timeofday, v_speed, v_behavior);
end if;
return QUERY
select * from CUSTOMBEHAVIOR where ... ;
-- commit;
end
$body$ LANGUAGE plpgsql;
PL/pgSQL 函数在事务内自动运行。要么全部成功,要么全部失败。参见:
如果需要,您可以捕获理论上可能发生的异常(但可能性很小)。
手册中有关捕获错误的详细信息。
您的功能已审核并简化:
CREATE FUNCTION foo(v_weather text
, v_timeofday text
, v_speed text
, v_behavior text)
RETURNS SETOF custombehavior
LANGUAGE plpgsql AS
$func$
BEGIN
DELETE FROM custombehavior
WHERE weather = 'RAIN'
AND timeofday = 'NIGHT'
AND speed = '45MPH';
INSERT INTO custombehavior
( weather, timeofday, speed, behavior)
SELECT v_weather, v_timeofday, v_speed, v_behavior
WHERE NOT EXISTS (
SELECT FROM defaultbehavior
WHERE a = 'RAIN'
AND b = 'NIGHT'
AND c = '45MPH'
);
RETURN QUERY
SELECT * FROM custombehavior WHERE ... ;
END
$func$;
如果您确实需要像标题中所示那样开始/结束事务,请查看 Postgres 11 或更高版本中的 SQL 过程(
CREATE PROCEDURE
)。 (但过程当前无法返回集合。)请参阅:
此外,您可能需要将
INSERT
和 DELETE
替换为“UPSERT”以安全地处理竞争条件。参见:
更新:PostgreSQL 11 版本之后。您可以在存储过程中控制事务。
====== 版本 10 之前:
START TRANSACTION;
select foo() ;
COMMIT;
“不幸的是 Postgres 没有存储过程,所以你总是需要在调用代码中管理事务” – a_horse_with_no_name