26

希望是一个简单的问题,但我还没有找到合适的答案。我被可靠地告知 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声明中,就像手册说的那样

重要的是不要将在 PL/pgSQL 中用于分组语句的 BEGIN/END 与用于事务控制的类似名称的 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说_ _

如果 SET TRANSACTION 在没有事先 START TRANSACTION 或 BEGIN 的情况下执行,它似乎没有任何效果,因为事务将立即结束。

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

手册还说:

在执行事务的第一个查询或数据修改语句(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

4 回答 4

20

你不能那样做。

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

于 2011-06-08T18:02:12.197 回答
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

请注意,在您的特定示例中,您可以使用第一个表上的触发器来执行此操作。只要确保行计数更新以一致的顺序完成以避免死锁,您就可以在可重复读取模式下做得很好。

我是标准的粉丝

PL/语言是平台特定的。

于 2011-06-08T08:51:33.497 回答
0

事务隔离意味着您可以访问在其他并发事务中所做的更改。

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

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

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

于 2011-06-08T14:10:08.320 回答
0

在 PG 中,您的程序不是单独的事务。即存储过程参与了现有事务。

BEGIN TRAN

SELECT 1;
SELECT my_proc(99);

ROLLBACK TRAN;

话虽如此,您必须在存储过程之外设置事务开始的事务级别。

一种选择是将服务器配置为在您最想使用的隔离环境中运行,并针对与服务器设置不同的边缘情况执行 SET。

于 2011-06-08T18:03:07.907 回答