PostgreSQL函數自動Commit/Rollback所帶來的問題


一、綜述

今天在PostgreSQL遇到一個奇怪的現象,簡而言之,是想用函數(存儲過程)實現插入記錄,整個過程沒報錯但事后卻沒找到記錄!忙活半天,才發現原因是PostgreSQL函數(存儲過程)有自動COMMIT或ROLLBACK的特殊規定。

二、問題重現

以下用示例表和示例代碼來重現該問題。

create table t1 
(
  ID int not null primary key,
  name varchar(20)
);

涉及的存儲過程是從oracle那邊直接拷貝過來后再修改過的,原先是動態SQL,這里簡化為靜態SQL。注意其中有個commit;根據PostgreSQL的要求,對事務增加begin...exception...end,否則會有錯誤或警告。示例腳本代碼為:

create or replace function p1(pid int, pname varchar)
returns void as $$
begin
  begin             --pg對事務的要求
  insert into t1 values(pid, pname);
  commit;
  exception      
  when others then
  end;              --pg對事務的要求
end;
$$ language plpgsql;

依次執行腳本創建存儲過程、調用存儲過程、查找示例表,結果如下: 

postgres=# \i test1.sql
CREATE FUNCTION
postgres=# select p1(1, 'abc');
 p1
----
(1 行記錄)

postgres=# select * from t1;

  id | name
  ----+------
  (0 行記錄)

 
        

要插入的記錄並不存在!驚喜不驚喜?意外不意外?

三、原因分析及解決

仔細查找有關資料,發現有這么一個解釋:

Functions and trigger procedures are always executed within a transaction established by an outer 
query — they cannot start or commit that transaction, since there would be no context for them to
execute in. However, a block containing an EXCEPTION clause effectively forms a subtransaction that
can be rolled back without affecting the outer transaction.

其意義是PostgreSQL的函數總是默認為一個事務,總是自動Commit或Rollback。

其實一開始沒增加begin...exception...end時,PostgreSQL報錯“can't begin/end transaction  in pl/pgsql”,已經隱含了這層信息。只是腦子里還是延續Oracle的習慣,而畫蛇添足了。

於是,修改存儲過程的腳本,按最簡單的法子來:

create or replace function p2(pid int, pname varchar)
returns void as $$
begin
  insert into t1 values(pid, pname);
end;
$$ language plpgsql;

為驗證此說法是否正確,在再次創建函數、調用函數后,增加一個回滾(事先已設置AutoCommit為false)的操作,然后再查詢記錄:

postgres=# \i test1.sql
CREATE FUNCTION
postgres=# select p2(1, 'abc');
 p2
----
(1 行記錄)

postgres=# rollback;
WARNING:  there is no transaction in progress
ROLLBACK
postgres=# select * from t1;
 id | name
----+------
  1 | abc
(1 行記錄)

可見,這次記錄已成功插入,且外部的回滾操作對其無影響。

四、總結

Oracle是可以在存儲過程或函數里指定Commit/Rollback的,如果沒有,則外部調用者可以回滾存儲過程內部的操作。

但在PostgreSQL,函數(存儲過程)總是自動將其所有操作當作一個事務,外部無法對內部操作提交或回滾。

問題好像已經解決,但留有一個疑問沒弄明白,為什么PostgreSQL允許在函數體中加關於事務的begin...exception...end,但結果卻好像是沒提交?


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM