PostgreSQL transaction restart

狂风中的少年 提交于 2019-12-24 03:02:14

问题


I'm starting to play with PostgreSQL and noticed that sequences never rollback, even on failed INSERT.
I've read that it is as expected to prevent duplicated sequences on concurrent transactions and I found that weird as my database experience is only with GTM where transaction restarts are common and used precisely for this.

So I wanted to test restarts in PGSQL and loaded this in a database:

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;

So, function mytest() will retrieve balance, wait 3 seconds (to let me launch the other process) and then update balance based on the saved variable.

I now launch 2 calls to this function directly from shell:

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)

I would expect balance to be 20, not 10, as the last transaction to be committed should restart as the "view" of balance from account where id=1 changed during processing...

I've read about transaction isolation in official documentation and it sounds to me that the default read committed should enforce this behavior precisely..
I've also tested changing the isolation level to serializable and then the last transaction committed does throw an exception but I would like to know if there isn't any "transaction restart" functionality (as I described) or if I'm missing something...


回答1:


You get a "restart" automatically if you use proper queries with 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:

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 takes a row level lock to announce the claim that this row is going to be updated. The same function trying the same in another transaction will be blocked and wait until the first commits or rolls back - then take the lock itself and build on the updated row so that the result of your experiment will be 20, not 10.

You can have the same much more efficiently with a plain and simple UPDATE query that takes the appropriate FOR UPDATE locks automatically:

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

These recent questions seem to have run into similar problems. Detailed explanation and links:

  • Function taking forever to run for large number of records
  • Lock for SELECT so another process doesn't get old data


来源:https://stackoverflow.com/questions/29366621/postgresql-transaction-restart

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!