PostgreSQL 事务重启

PostgreSQL transaction restart

我开始使用 PostgreSQL 并注意到 sequences 从不回滚,即使在失败时 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,因为要提交的最后一个事务应该重新启动,因为 balance from account where id=1 的 "view" 在处理过程中发生了变化...

我读过 transaction isolation in official documentation,在我看来默认的 read committed 应该精确地执行此行为..
我还测试了将隔离级别更改为 serializable 然后最后提交的事务确实抛出异常但我想知道是否没有任何 "transaction restart" 功能(如我所述)或者是否我错过了一些东西...

如果您使用 row level locks. To be precise, the transaction is not restarted as a whole, it just waits its turn when trying to lock a row in default transaction isolation READ COMMITTED:

进行适当的查询,您会自动获得 "restart"
CREATE OR REPLACE FUNCTION mytest()
   RETURNS integer AS
$func$
DECLARE
   cc integer;
BEGIN
   SELECT INTO cc balance FROM account WHERE id = 1 <b>FOR UPDATE</b>;

   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.

您可以使用自动采用适当 FOR UPDATE locks 的简单 UPDATE 查询更有效地 很多

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

这些最近的问题似乎 运行 变成了类似的问题。详细解释和链接:

  • Function taking forever to run for large number of records