为postgresql存储过程设置隔离级别

时间:2011-06-08 05:11:14

标签: postgresql stored-procedures isolation-level

希望这是一个简单的问题,但我还没有找到一个体面的答案。我可靠地告知PostgreSQL(特别是版本9.0.4)中的存储过程(用户定义的DB函数)本质上是事务性的,因为它们是通过SELECT语句调用的,而SELECT语句本身就是一个事务。那么如何选择存储过程的隔离级别呢?我相信在其他DBMS中,所需的事务块将被包装在START TRANSACTION块中,其中所需的隔离级别是可选参数。

作为一个具体的例子,说我想这样做:

CREATE FUNCTION add_new_row(rowtext TEXT)
RETURNS VOID AS 
$$
BEGIN
        INSERT INTO data_table VALUES (rowtext);
        UPDATE row_counts_table SET count=count+1;
END;
$$  
LANGUAGE plpgsql
SECURITY DEFINER;

想象一下,我想确保此函数始终作为可序列化事务执行(是的,是的,PostgreSQL SERIALIZABLE不是可正确序列化的,但这不是重点)。我不想要求它被称为

START TRANSACTION ISOLATION LEVEL SERIALIZABLE;
SELECT add_new_row('foo');
COMMIT;

那么如何将所需的隔离级别下移到函数中?我相信我不能将隔离级别放在BEGIN语句中,如the manual says

  

重要的是不要混淆使用   BEGIN / END用于分组语句   在PL / pgSQL中使用类似命名的   用于事务控制的SQL命令。   PL / pgSQL的BEGIN / END仅适用于   分组;他们没有开始或结束   交易。功能和触发器   程序总是在其中执行   外部建立的交易   查询 - 他们无法启动或提交   那个交易,因为会有   没有他们执行的上下文。

对我来说最明显的方法是在函数定义中的某处使用SET TRANSACTION,例如:

CREATE FUNCTION add_new_row(rowtext TEXT)
RETURNS VOID AS 
$$
BEGIN
        SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
        INSERT INTO data_table VALUES (rowtext);
        UPDATE row_counts_table SET count=count+1;
END;
$$  
LANGUAGE plpgsql
SECURITY DEFINER;

虽然这会被接受,但我还不清楚这可以依靠它来工作。 SET TRANSACTION的{​​{3}}表示

  

如果没有执行SET TRANSACTION   它是一个先前的START TRANSACTION或BEGIN   似乎没有任何效果,因为   交易将立即结束。

这让我感到困惑,因为如果我调用一个单独的SELECT add_new_row('foo');语句,我希望(假设我没有禁用自动提交)SELECT将作为具有会话默认隔离级别的单行事务运行。

documentation也说:

  

事务隔离级别不能   在第一次查询后更改或   数据修改语句(SELECT,   INSERT,DELETE,UPDATE,FETCH或   COPY)的交易一直是   执行。

如果在具有较低隔离级别的事务中调用该函数会发生什么,例如:

START TRANSACTION ISOLATION LEVEL READ COMMITTED;
UPDATE row_counts_table SET count=0;
SELECT add_new_row('foo');
COMMIT;

对于奖金问题:该功能的语言是否有所不同?是否可以在PL / pgSQL中设置隔离级别与普通SQL不同?

我很喜欢标准并记录最佳实践,所以任何体面的参考都会受到赞赏。

4 个答案:

答案 0 :(得分:17)

你做不到。

您可以做的是让您的函数检查当前事务隔离级别是什么,如果不是您想要的那个,则中止。您可以通过运行SELECT current_setting('transaction_isolation')然后检查结果来执行此操作。

答案 1 :(得分:1)

该功能的语言没有任何区别。

这失败了:

test=# create function test() returns int as $$
  set transaction isolation level serializable;
  select 1;
$$ language sql;
CREATE FUNCTION
test=# select test();
ERROR:  SET TRANSACTION ISOLATION LEVEL must be called before any query
CONTEXT:  SQL function "test" statement 1

请注意,在您的特定示例中,您可以使用第一个表上的触发器执行此操作。只需确保完成行计数更新in a consistent order以避免死锁,并且您可以在可重复读取模式下正常工作。

  

我是标准的粉丝

PL /语言是特定于平台的。

答案 2 :(得分:0)

在PG中,您的程序不是单独的交易。也就是说,存储过程参与现有事务。

BEGIN TRAN

SELECT 1;
SELECT my_proc(99);

ROLLBACK TRAN;

据说,你必须设置事务开始的事务级别,这是在存储过程之外的。

一种选择是将服务器配置为在您最常使用的隔离区中运行,并为不同于服务器设置的边缘情况执行SET。

答案 3 :(得分:-1)

交易隔离是指您可以访问的其他可信交易中所做的更改。

如果要序列化执行,则必须使用锁。

您可以使用行后触发和更新计数。 “UPDATE row_counts_table”将锁定表,并且所有事务都将被序列化。 很慢。

在您的示例中,您有两个陈述。执行插入但更新必须等待其他事务,并且计数在此期间无效。

相关问题