To use autonomous transactions, you need to enable required extension:
CREATE EXTENSION IF NOT EXISTS pg_background;
In the following example, we'll use autonomous transactions to write log records for a job execution. Assume we have a table that stores job statuses:
DROP TABLE IF EXISTS BATCH_JOB_STATUS; CREATE TABLE BATCH_JOB_STATUS ( JOB_NAME varchar primary key, STATUS_IND char(1), UPDT_TIME TIMESTAMPTZ );
Next, we want to make a PL/pgSQL function that can be called from regular procedure code but always commits, regardless of what happens in the surrounding transaction. Here’s a simple approach that builds up the SQL as a dynamic string and then executes it using pg_background, waiting for the result:
-- simple function to update status in background CREATE or REPLACE FUNCTION batch_job_set_status ( P_JOB_NAME IN BATCH_JOB_STATUS.JOB_NAME%TYPE, P_STATUS_IND IN BATCH_JOB_STATUS.STATUS_IND%TYPE ) RETURNS VOID AS $FUNCBODY$ DECLARE v_query text; BEGIN v_query = 'INSERT INTO BATCH_JOB_STATUS' || ' (JOB_NAME, STATUS_IND, UPDT_TIME)' || ' VALUES (' || quote_nullable(P_JOB_NAME) || ', ' || quote_nullable(P_STATUS_IND) || ', ' || 'NOW())' || ' ON CONFLICT (JOB_NAME) DO UPDATE' || ' SET STATUS_IND = ' || quote_nullable(P_STATUS_IND) || ' , UPDT_TIME = NOW()' ; -- execute function as autonomous transaction "in background" -- and wait for the result PERFORM * FROM pg_background_result(pg_background_launch(v_query)) AS (result TEXT); END; $FUNCBODY$ LANGUAGE plpgsql;
This shows the key usage sequence for autonomous transactions – use pg_background_launch()
to
execute a SQL statement in the background. In this case we wait for the result by wrapping the call with
pg_background_result()
. If this was truly a long-running background, and we wanted to
check for the status and result, we could store the ID returned from launch()
for a
later call the result()
.
In this case, we built the full upsert statement as a dynamic SQL string. If the logic to be executed in
the background was more complex, you would want to make an "inner" PL/pgSQL function and just dynamically
build up the SQL string to call the autonomous function.
In the naive approach, if we try to directly update the status inside a transaction that rolls back, we lose the update we just made:
postgres=# BEGIN; BEGIN postgres=# SELECT * FROM BATCH_JOB_STATUS WHERE JOB_NAME = 'my job'; job_name | status_ind | updt_time ----------+------------+------------------------------- my job | R | 2017-05-08 15:59:23.774083-04 (1 row) postgres=# UPDATE BATCH_JOB_STATUS SET STATUS_IND = 'C' WHERE JOB_NAME = 'my job'; UPDATE 1 postgres=# SELECT * FROM BATCH_JOB_STATUS WHERE JOB_NAME = 'my job'; job_name | status_ind | updt_time ----------+------------+------------------------------- my job | C | 2017-05-08 15:59:23.774083-04 (1 row) postgres=# ROLLBACK; ROLLBACK postgres=# SELECT * FROM BATCH_JOB_STATUS WHERE JOB_NAME = 'my job'; job_name | status_ind | updt_time ----------+------------+------------------------------- my job | R | 2017-05-08 15:59:23.774083-04 (1 row)
But using background execution we are able to run an autonomous transaction and keep the update, even though the surrounding transaction rolls back.
postgres=# BEGIN; BEGIN postgres=# SELECT * FROM batch_job_set_status('my job', 'C'); batch_job_set_status ---------------------- (1 row) postgres=# SELECT * FROM BATCH_JOB_STATUS WHERE JOB_NAME = 'my job'; job_name | status_ind | updt_time ----------+------------+------------------------------- my job | C | 2017-05-08 22:28:01.344463-04 (1 row) postgres=# ROLLBACK; ROLLBACK postgres=# SELECT * FROM BATCH_JOB_STATUS WHERE JOB_NAME = 'my job'; job_name | status_ind | updt_time ----------+------------+------------------------------- my job | C | 2017-05-08 22:28:01.344463-04 (1 row)