From PostgreSQL version 10 and onward (i.e., all supported versions as of May, 2022), there is, besides the txid_current()
function already mentioned by @Gajus, also a txid_current_if_assigned()
function, which returns “NULL if no [transaction] ID is assigned yet” allowing you “to avoid unnecessary consumption of an XID” (which is the problem that @Gajus was hinting at in his answer from 2018).
In fact, since PostgreSQL 13, these txid_
-prefixed functions are deprecated in favor of pg_current_xact_id()
and pg_current_xact_id_if_assigned()
.
The reason that, if you just want to find out if you are currently inside of a transaction, it's better to use pg_current_xact_id_if_assigned()
than pg_current_xact_id()
is because, as a rule of thumb, you want to avoid unnecessary XID consumption. GitLab, for instance, ran into problems because they used save points too extensively.
However, a caveat also applies to pg_current_xact_id_if_assigned()
. It will return NULL
as long as nothing has been written within your transaction, because PostgreSQL doesn't assign a transaction ID until a write operation occurs. A possible workaround is to create a temporary table that is bound the transaction:
SELECT pg_current_xact_id_if_assigned() IS NOT NULL AS is_transaction;
is_transaction
----------------
f
(1 row)
BEGIN TRANSACTION; -- Making it explicitly READ WRITE won't make a difference.
SELECT pg_current_xact_id_if_assigned() IS NOT NULL AS is_transaction;
is_transaction
----------------
f
(1 row)
CREATE TEMPORARY TABLE a (b int) ON COMMIT DROP;
SELECT pg_current_xact_id_if_assigned() IS NOT NULL AS is_transaction;
is_transaction
----------------
t
(1 row)
There you have it.
If you where to run the CREATE TEMPORARY TABLE … ON COMMIT DROP
statement outside of a transaction, it will have disappeared by the end of the implicit transaction that each mutating statement (in auto-commit-mode) is executed in.
Edit 1: Do note that you cannot wrap this logic into a function or procedure, because the function/procedure call statement will be wrapped in an implicit transaction, and thus your am_i_inside_a_transaction()
function would always return TRUE
.
Edit 2: I want to make it clear that, besides the informational value of my answer, I do as of yet believe the comparing timestamps method from @Gajus his own answer to be superior to the pg_current_xact_id_if_assigned()
method. And his method can be made even more explicit by using transaction_timestamp()
instead of now()
:
CREATE FUNCTION in_transaction() RETURNS boolean
LANGUAGE SQL
RETURN transaction_timestamp() != statement_timestamp()
;
SELECT in_transaction();
in_transaction
----------------
f
(1 row)
BEGIN;
SELECT in_transaction();
in_transaction
----------------
t
(1 row)
A caveat applies here too, though, since statement_timestamp()
is “the time of receipt of the latest command message from the client”. That means that if you are to nest the above function call in another function, the function will always return FALSE
.