Search Postgresql Archives

Re: Why must AUTOCOMMIT be ON to do txn control in plpgsql procedure?

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On 8/6/19 1:17 PM, Bryn Llewellyn wrote:
I read this blog post

*PostgreSQL 11 – Server-side Procedures — Part 1 <https://www.2ndquadrant.com/en/blog/postgresql-11-server-side-procedures-part-1/> and Part 2 <https://www.2ndquadrant.com/en/blog/postgresql-11-server-side-procedures-part-2/>*

It starts with/ “Thanks to the work done by 2ndQuadrant contributors, we now have the ability to write Stored Procedures in PostgreSQL… once PostgreSQL 11 comes out”/. It focuses on doing txn control from a stored proc.

In my initial test of the code that it presents, I got the runtime error “*invalid transaction termination*” from the first-encountered txn control statement (that happened to be *commit*). I presently realized that this was because I had *\set AUTOCOMMIT OFF*in my *.psqlrc*startup file. Once I changed this, the code worked as the blog described.

I’m hoping that someone from 2ndQuadrant can answer my questions about what my tests show. They use a trivial table created thus:

*create table t(n integer);
*
My first test uses this:

*create or replace procedure p1()
   language plpgsql
as $$
begin
   insert into t(n) values(17);
end;
$$;
*
I test it first with *AUTOCOMMIT OFF*and then with in *ON*. The results are exactly as I’d expect.

When it’s *ON*, the effect of the *insert*shows up with a *select*immediately after the *call*finishes. Then *rollback*really does wipe out the effect of the *insert*, as is shown with a subsequent *select*.

And with *AUTOCOMMIT ON*, the effect of the *insert* again shows up with a *select* immediately after the *call* finishes. But now a subsequent *rollback*causes “*WARNING: there is no transaction in progress*”. The effect of the *insert*was already committed.

I could add a sleep after the *insert*and then watch from a second session. For now, I’m assuming that the effect of *AUTOCOMMIT ON*takes place when the *call*finishes and not immediately after the *insert*.

My second test uses this:

*create or replace procedure p2()
   language plpgsql
as $$
   declare
     levl_1 varchar(20);
     levl_2 varchar(20);
   begin
     -- This "rollback" is critical.
    -- Else "SET TRANSACTION ISOLATION LEVEL must be called before any query".
     rollback;

     set transaction isolation level repeatable read;
     show transaction isolation level into levl_1;
     insert into t(n) values(17);
     rollback;

     set transaction isolation level serializable;
     show transaction isolation level into levl_2;
     insert into t(n) values(42);
     commit;

     raise notice 'isolation level #1: %', levl_1;
     raise notice 'isolation level #2: %', levl_2;
   end;
$$;
*
(I got into this because I want to encapsulate all the logic that changes a table which has, in my functional spec, the table-level data rule: exactly one or two rows where column c1 has value ‘x’. The idea is to use the “serializable” isolation level and finish with a query that tests the rule. I’ll do this in an infinite loop with a sleep to that if a concurrent execution of the same proc pre-empts me and I get the “*could not serialize…*” error, I’ll hande the exception and go round the loop again, exiting only when I don’t get the exception.)

As mentioned above, I must call this with *AUTOCOMMIT ON*to avoid a runtime error. See my comment: the *rollback*as the proc’s very first executable statement is critical. The code runs without error and shows the result that I expect.

I’m attaching *txn_control_in_plpgsql_proc.sql*. It’s self-contained (can be run time and again with the same outcome) and implements what I just described. I’m also attaching *txn_control_in_plpgsql_proc.txt*. This is the stdout output, copied from the terminal window, produced when I invoke *psql*to start my *.sql*script from the command line.

B.t.w.. I have a real implementation of what I wanted to achieve and it seems to work perfectly. So I’ve achieved my goal. But I hate the fact that I got there by trial and error and cannot rehearse a mental model that informs me why what I wrote is the proper approach.

*Please describe the rules for all this and reassure me that I can rely on the behavior that I implemented by starting with **rollback**in my proc.*

https://www.postgresql.org/docs/11/app-psql.html
"AUTOCOMMIT

When on (the default), each SQL command is automatically committed upon successful completion. To postpone commit in this mode, you must enter a BEGIN or START TRANSACTION SQL command. When off or unset, SQL commands are not committed until you explicitly issue COMMIT or END. The autocommit-off mode works by issuing an implicit BEGIN for you, just before any command that is not already in a transaction block and is not itself a BEGIN or other transaction-control command, nor a command that cannot be executed inside a transaction block (such as VACUUM)."

The way I understand it in your first case:
\set AUTOCOMMIT off
-- get clean start
begin;
delete from t;
commit;

call p1();
select n from t order by n;
rollback;
select n from t order by n;
rollback;

You have a implied BEGIN; before the begin; you wrap the delete in. Therefore you can do the rollback;. In the AUTOCOMMIT on there is only on transaction and it ends with the commit; after the delete. So when you attempt the rollback you get the error. REMEMBER in plpgsql Begin is not for transaction control:
https://www.postgresql.org/docs/11/plpgsql-transactions.html

Have not worked through the second case yet.





.



--
Adrian Klaver
adrian.klaver@xxxxxxxxxxx





[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Index of Archives]     [Postgresql Jobs]     [Postgresql Admin]     [Postgresql Performance]     [Linux Clusters]     [PHP Home]     [PHP on Windows]     [Kernel Newbies]     [PHP Classes]     [PHP Books]     [PHP Databases]     [Postgresql & PHP]     [Yosemite]

  Powered by Linux