暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

PostgreSQL Oracle 兼容性之 - plpgsql, pl/sql 存储过程、函数内事务处理 , block , exception

digoal 2019-05-31
614
TAG 21

作者

digoal

日期

2019-05-31

标签

PostgreSQL , autonomous_transaction , 自治事务 , Oracle兼容性 , plpgsql , block , exception


背景

Oracle用户在函数中或存储过程中会用到自治事务,例如savepoint, rollback to,在处理数据的过程中,根据业务逻辑的需求,选择性的回退变更到设定的savepoint。

PostgreSQL的plpgsql函数语言,与Oracle的使用方法略有不同。

https://www.postgresql.org/docs/current/plpgsql-porting.html

首先需要熟悉几个概念

概念

1、一个plpgsql函数是一个独立的大事务。但是内部又可以有子事务(通过block控制)。

2、块(block)

<<block>> declare begin exception end;

一个块被切成了两份,begin和exception,事务状态可能有几种情况:要么begin里面全部提交,要么begin里面全部回滚exception里面全部提交,要么全部回滚

1、当begin里面没有异常时,全部提交

2、当begin里面有异常,exception里面没有异常时,begin里面全部回滚exception里面全部提交

3、begin和exception都有异常时,begin和exception里面全部回滚

了解以上知识点,下面看几个例子

例子

create table t (id int, cnt int); insert into t values (1, 100);

这个表有1条记录,更新CNT,如果更新后的CNT变成负数,就回退这笔更新,并继续。

写一个函数,一共4个BLOCK

block0在最外层,包含了block1,block2,block3

```
create or replace function f_test(int,int,int,int,int) returns void as $$
<>
declare
tmp int;
begin
<>
declare
begin
update t set cnt=cnt-$2 where id=$1 returning cnt into tmp;
if tmp <0 then
raise exception '';
end if;
exception when others then
raise notice 'block1, 已扣减到负数, 回退当前的begin block,并继续后面的BEGIN BLOCK';
end;

<>
declare
begin
update t set cnt=cnt-$3 where id=$1 returning cnt into tmp;
if tmp <0 then
raise exception '';
end if;
exception when others then
raise notice 'block2, 已扣减到负数, 回退当前的begin block';
-- 有没有办法回退到当前BEGIN BLOCK以外的其他位置?并继续后面的BEGIN BLOCK
-- 没有
-- 也就是说如果block1成功了的话,就没法回退block1了,除非在block0的BODY里面回退整个block0
end;

<>
declare
begin
update t set cnt=cnt-$4 where id=$1 returning cnt into tmp;
if tmp <0 then
raise exception '';
end if;
exception when others then
raise notice 'block3, 已扣减到负数, 回退当前的begin block';
end;

update t set cnt=cnt-$5 where id=$1 returning cnt into tmp;
if tmp <0 then
raise exception '';
end if;
exception when others then
raise notice '如果触发当前exception,则回退到顶层begin block之前的状态';
-- 如果到这里,会回退整个block0,包括了block1,block2,block3
end;
$$ language plpgsql strict;
```

例子

1、block0,block1,block2的内容完整执行,block3回退。

```
postgres=# select f_test(1,8,9,200,0);
NOTICE: block3, 已扣减到负数, 回退当前的begin block
f_test


(1 row)

postgres=# select * from t;
id | cnt
----+-----
1 | 83
(1 row)
```

2、block0,block1,block2的内容完整执行,block3回退。

```
postgres=# select f_test(1,8,9,200,0);
NOTICE: block3, 已扣减到负数, 回退当前的begin block
f_test


(1 row)

postgres=# select * from t;
id | cnt
----+-----
1 | 66
(1 row)
```

3、block0,block1的内容完整执行,block2, block3回退。

```
postgres=# select f_test(1,8,900,200,0);
NOTICE: block2, 已扣减到负数, 回退当前的begin block
NOTICE: block3, 已扣减到负数, 回退当前的begin block
f_test


(1 row)

postgres=# select * from t;
id | cnt
----+-----
1 | 58
(1 row)
```

4、block0,block1,block2, block3的内容完整执行。

```
postgres=# select f_test(1,8,9,2,0);
f_test


(1 row)

postgres=# select * from t;
id | cnt
----+-----
1 | 39
(1 row)
```

5、block0全部回退。

```
postgres=# select f_test(1,8,9,2,100);
NOTICE: 如果触发当前exception,则回退到顶层begin block之前的状态
f_test


(1 row)

postgres=# select * from t;
id | cnt
----+-----
1 | 39
(1 row)
```

问题

有没有办法在一个block中回退到当前BEGIN BLOCK以外的其他位置的状态?并继续执行后面的BEGIN BLOCK

没有

所以,下面这张方式,目前没有办法实现。

```
create or replace function f_test(int,int,int,int) returns void as $$
<>
declare
tmp int;
savepoint a; -- 目前没法实现
begin
<>
declare
begin
update t set cnt=cnt-$2 where id=$1 returning cnt into tmp;
if tmp <0 then
raise exception '';
end if;
exception when others then
raise notice 'block1, 已扣减到负数, 回退当前的begin block,并继续后面的BEGIN BLOCK';
rollback to a; -- 目前没法实现,这里只能回退block1的body
end;

<>
declare
begin
update t set cnt=cnt-$3 where id=$1 returning cnt into tmp;
if tmp <0 then
raise exception '';
end if;
exception when others then
raise notice 'block2, 已扣减到负数, 回退当前的begin block';
-- 有没有办法回退到当前BEGIN BLOCK以外的其他位置?并继续后面的BEGIN BLOCK
-- 没有
rollback to a; -- 目前没法实现,这里只能回退block2的body
end;

<>
declare
begin
update t set cnt=cnt-$4 where id=$1 returning cnt into tmp;
if tmp <0 then
raise exception '';
end if;
exception when others then
raise notice 'block3, 已扣减到负数, 回退当前的begin block';
rollback to a; -- 目前没法实现, 这里只能回退block3的body
end;

exception when others then
raise notice '如果触发当前exception,则回退到顶层begin block之前的状态';
end;
$$ language plpgsql strict;
```

也许将来的版本会支持以上用法。

其他,子事务拆分成多个函数,通过DBLINK调用,通过dblink或notify,listen同步不同子事务的完成情况。

拆分子事务存储过程

savepoint

rollback

监测子事务状态(使用notify或dblink,同步状态),选择回退或继续

需要注意notify lock问题

https://www.postgresql-archive.org/PATCH-Improve-performance-of-NOTIFY-over-many-databases-issue-blocking-on-AccessExclusiveLock-on-obj-td6088908.html

参考

《PostgreSQL 11 preview - 增加CREATE PROCEDURE和CALL语法 (含过程内commit/rollback, 自治)》

《PostgreSQL 10.0 preview 功能增强 - 匿名、自治事务(Oracle 兼容性)(background session)》

《PostgreSQL Oracle 兼容性之 - plpgsql 自治事务(autonomous_transaction)补丁》

《PostgreSQL Oracle 兼容性之 - 函数 自治事务 的写法和实现》

《PostgreSQL Oracle 兼容性 自治事务 - partial/sub commit within function》

《Compare dblink module Within One Transaction in PostgreSQL - dblink, 自治事务(Oracle兼容性)》

https://www.postgresql.org/docs/current/plpgsql-porting.html

PostgreSQL 许愿链接

您的愿望将传达给PG kernel hacker、数据库厂商等, 帮助提高数据库产品质量和功能, 说不定下一个PG版本就有您提出的功能点. 针对非常好的提议,奖励限量版PG文化衫、纪念品、贴纸、PG热门书籍等,奖品丰富,快来许愿。开不开森.

9.9元购买3个月阿里云RDS PostgreSQL实例

PostgreSQL 解决方案集合

德哥 / digoal's github - 公益是一辈子的事.

digoal's wechat

文章转载自digoal,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论