Skip to content

Latest commit

 

History

History
264 lines (206 loc) · 7.98 KB

20160203_02.md

File metadata and controls

264 lines (206 loc) · 7.98 KB

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

作者

digoal

日期

2016-02-03

标签

PostgreSQL , Oracle , 兼容性 , 自治事务


背景

使用Oracle的用户,在函数中如果使用了自治事务的话,如果要转到PostgreSQL会遇到很棘手的问题。

因为PostgreSQL的函数是作为一个事务来处理的,要么全部提交,要么全部回滚,

除了exception,每个exception是一个子事务。

因此使用exception可以达到自治事务的目的。

例子1,

使用并行block和嵌套block,来控制子事务层级。

输入参数为block1, block2.1, block2.2, block3.1 。

这些参数代表执行在哪个block出错,出错时对应层级的block的exception会捕获错误,同时处理,然后跳到下一个block继续执行。

如果是外层的block出错,内层还没有被执行的block就没机会执行了。

根据业务需求,调整block层级或嵌套层级,达到目的。

这种用法可以完美的支撑业务的需求。

(除了一种情况不能满足,就是被提交的子事务立刻可以被其他事务可见。这种需求建本文下面的方法,用dblink来满足这种需求即可。)

create or replace function ft(err_level text) returns void as $$  
declare  
begin -- block level 1  
  raise notice 'block level 1';  
  if (err_level='block1') then  
    raise exception '%', err_level;  
  end if;  
  
  begin -- block level 2.1  
    raise notice 'block level 2.1';  -- 请用业务处理SQL代替  
    if (err_level='block2.1') then  
      raise exception '%', err_level;  
    end if;  
  
    begin -- block level 3.1  
      raise notice 'block level 3.1';  
      if (err_level='block3.1') then  
        raise exception '%', err_level;  
      end if;  
      exception when others then  -- you can write catchup any ERROR CODE or ERROR STATE.  
        raise notice 'end block level 3.1';  
    end; -- end block level 3.1  
  
    exception when others then  -- you can write catchup any ERROR CODE or ERROR STATE. 回滚block 2.1的业务处理SQL  
      raise notice 'end block level 2.1';  
  end; -- end block level 2.1  
  
  begin -- block level 2.2  
    raise notice 'block level 2.2';  
    if (err_level='block2.2') then  
      raise exception '%', err_level;  
    end if;  
    exception when others then  -- you can write catchup any ERROR CODE or ERROR STATE.  
      raise notice 'end block level 2.2';  
  end; -- end block level 2.2  
  
  exception when others then  -- you can write catchup any ERROR CODE or ERROR STATE.  
    raise notice 'end block level 1';  
end; -- end block level 1  
$$ language plpgsql;  

测试:

在block 1出错,出错代码后面的代码都不会被执行。

postgres=# select ft('block1');  
NOTICE:  block level 1  
NOTICE:  end block level 1  
 ft   
----  
   
(1 row)  

在block2.1出错,block 2.1内部出错代码后面的代码都不会被执行。但是同级代码如2.2会被执行。

postgres=# select ft('block2.1');  
NOTICE:  block level 1  
NOTICE:  block level 2.1  
NOTICE:  end block level 2.1  
NOTICE:  block level 2.2  
 ft   
----  
   
(1 row)  

在block2.2出错。

postgres=# select ft('block2.2');  
NOTICE:  block level 1  
NOTICE:  block level 2.1  
NOTICE:  block level 3.1  
NOTICE:  block level 2.2  
NOTICE:  end block level 2.2  
 ft   
----  
   
(1 row)  

在block3.1出错。

postgres=# select ft('block3.1');  
NOTICE:  block level 1  
NOTICE:  block level 2.1  
NOTICE:  block level 3.1  
NOTICE:  end block level 3.1  
NOTICE:  block level 2.2  
 ft   
----  
   
(1 row)  

更直观的例子:

drop table tt;  
create table tt(id int primary key, info text);  
insert into tt values(5,'test');  
  
create or replace function ft() returns void as $$  
declare  
begin -- block level 1  
  
  begin -- block level 2.1  
    insert into tt values (1,'test'),(2,'test'),(3,'test');  
    exception when others then   
      raise notice 'rollback block level 2.1';  
  end; -- end block level 2.1  
  
  begin -- block level 2.2  
    insert into tt values (4,'test'),(5,'test'),(6,'test'); -- 主键冲突, 插入失败, 但是不影响后面的block继续执行.  
    exception when others then   
      raise notice 'rollback block level 2.2';  
  end; -- end block level 2.2  
  
  begin -- block level 2.3  
    insert into tt values (7,'test'),(8,'test'),(9,'test');  
    exception when others then   
      raise notice 'rollback block level 2.3';  
  end; -- end block level 2.3  
  
  exception when others then  -- you can write catchup any ERROR CODE or ERROR STATE.  
    raise notice 'rollback block level 1';  
end; -- end block level 1  
$$ language plpgsql;  
  
postgres=# select ft();  
NOTICE:  rollback block level 2.2  
 ft   
----  
   
(1 row)  
  
postgres=# select * from tt;  
 id | info   
----+------  
  5 | test  
  1 | test  
  2 | test  
  3 | test  
  7 | test  
  8 | test  
  9 | test  
(7 rows)  

例子2:

使用dblink,同样需要将需要批量提交的部分写成子函数先。

例如 :

create extension dblink;  
CREATE SERVER fdtest FOREIGN DATA WRAPPER dblink_fdw OPTIONS (hostaddr '127.0.0.1', dbname '函数所在的库名');  
CREATE USER MAPPING FOR 需要调用函数的用户名 SERVER fdtest OPTIONS (user '需要调用函数的用户名', password '用户密码');  
GRANT USAGE ON FOREIGN SERVER fdtest TO 需要调用函数的用户名;  

函数体

declare  
  dblink_block_res1 record;  
  dblink_block_res2 record;  
...  
  dblink_block_resn record;  
...  
  其他变量定义;  
begin  
-- 建立连接  
if ( dblink_connect('myconn', 'fdtest') <> 'OK' ) then  
  raise notice '连接失败';  
  return;  
end if;  
-- block 1,需要流转的变量通过参数传入下面的函数  
  select dblink('myconn', 'select func1($1,$2,...)') into dblink_block_res1;  -- $1,$2,...使用常数替代, 或动态SQL  
-- 中间结果判断  
  if not found then  -- dblink调用异常  
    -- 异常处理  
  else  
    -- dblink对应子事务已提交  
  end if;  
-- block 2,需要流转的变量通过参数传入下面的函数  
  select dblink('myconn', 'select func2($1,$2,...)') into dblink_block_res2;  -- $1,$2,...使用常数替代, 或动态SQL  
-- 中间结果判断  
  if not found then  -- 远程调用异常  
    -- 异常处理  
  else  
    -- dblink对应子事务已提交  
  end if;  
......  
-- block n,需要流转的变量通过参数传入下面的函数  
  select dblink('myconn', 'select funcn($1,$2,...)') into dblink_block_resn;  -- $1,$2,...使用常数替代, 或动态SQL  
-- 中间结果判断  
  if not found then  -- 远程调用异常  
    -- 异常处理  
  else  
    -- dblink对应子事务已提交  
  end if;  
......  
exception when others then  
...  
end;  

其他参考地址:

http://www.postgresql.org/docs/9.5/static/contrib-dblink-function.html

http://postgresql.nabble.com/Autonomous-Transaction-WIP-td5798928.html

https://lwn.net/Articles/648973/

Count