PostgreSQL事务重启

时间:2015-03-31 10:36:24

标签: postgresql transactions plpgsql

我开始玩PostgreSQL并注意到sequence永远不会回滚,即使失败的INSERT也是如此。 我已经读过,这是预期会阻止并发事务上的重复序列,我发现这很奇怪,因为我的数据库经验只有GTM,其中事务重新启动是常见的,并且恰好用于此。

所以我想在PGSQL中测试重启并将其加载到数据库中:

CREATE SEQUENCE account_id_seq;

CREATE TABLE account
(
  id integer NOT NULL DEFAULT nextval('account_id_seq'),
  title character varying(40) NOT NULL,
  balance integer NOT NULL DEFAULT 0,
  CONSTRAINT account_pkey PRIMARY KEY (id)
);

INSERT INTO account (title) VALUES ('Test Account');

CREATE OR REPLACE FUNCTION mytest() RETURNS integer AS $$
DECLARE
    cc integer;
BEGIN
    cc := balance from account where id=1;

    RAISE NOTICE 'Balance: %', cc;
    perform pg_sleep(3);

    update account set balance = cc+10 where id=1 RETURNING balance INTO cc;

    return cc;
END
$$
LANGUAGE plpgsql;

因此,函数mytest()将检索余额,等待3秒(让我启动其他进程),然后根据保存的变量更新余额。

我现在直接从shell启动2次调用此函数:

void$ psql -c "select * from account where id=1"
 id |    title     | balance 
----+--------------+---------
  1 | Test Account |       0
(1 row)

void$ psql -c "select mytest()" & PIDA=$! && psql -c "select mytest()" && wait $PIDA
[1] 3312
NOTICE:  Balance: 0
NOTICE:  Balance: 0
 mytest 
--------
     10
(1 row)

 mytest 
--------
     10
(1 row)

[1]+  Done                    psql -c "select mytest()"
void$ psql -c "select * from account where id=1"
 id |    title     | balance 
----+--------------+---------
  1 | Test Account |      10
(1 row)

我希望余额为20而不是10,因为要提交的最后一个事务应该重新启动,因为" view"在处理期间balance from account where id=1发生了变化......

我已经阅读了transaction isolation in official documentation,并且听起来默认read committed应该正确执行此行为。
我还测试过将隔离级别更改为serializable,然后最后一次提交的事务确实抛出异常,但我想知道是否有任何"事务重启"功能(正如我所描述的)或者如果我遗漏了某些东西......

1 个答案:

答案 0 :(得分:2)

如果对row level locks使用正确的查询,则会自动“重启”。确切地说,事务没有作为一个整体重新启动,它只是在尝试锁定default transaction isolation READ COMMITTED中的行时等待它:

CREATE OR REPLACE FUNCTION mytest()
   RETURNS integer AS
$func$
DECLARE
   cc integer;
BEGIN
   SELECT INTO cc balance FROM account WHERE id = 1 FOR UPDATE;

   RAISE NOTICE 'Balance: %', cc;
   PERFORM pg_sleep(3);

   UPDATE account SET balance = cc+10
   WHERE id = 1
   RETURNING balance
   INTO cc;

   RETURN cc;
END
$func$  LANGUAGE plpgsql;

SELECT ... FOR UPDATE采用行级锁定来声明此行将要更新的声明。在另一个事务中尝试相同的功能将被阻止并等到第一次提交或回滚 - 然后获取锁本身并在更新的行上构建,以便实验结果为20,而不是10。

使用简单明了的UPDATE查询可以更有效地使用相同的 ,自动采用相应的FOR UPDATE locks

UPDATE account
SET    balance = balance + 10
WHERE  id = 1
RETURNING  balance;

这些最近的问题似乎遇到了类似的问题。详细说明和链接: