I found an answer.
There were two parts to this.
Firstly, what is important is virtualtransaction
rather than virtualxid
. Even though virtualxid
and transactionid
are null for everything I care about, they all share a virtualtransaction
id.
Secondly, although txid_current()
returns a transactionid
, which seems like it wouldn't help because my locks all have null for that, we can still use it to get what we want.
What happens is when you call txid_current()
postgres assigns a lock with the transactionid
returned by that function, which we can see if we query pg_locks
. This lock will share a virtualtransaction
id with everything else in the current transaction.
For example:
> BEGIN;
BEGIN
> SELECT locktype,transactionid,virtualtransaction,mode FROM pg_locks;
locktype | transactionid | virtualtransaction | mode
------------+---------------+--------------------+-----------------
relation | [NULL] | 3/38672 | AccessShareLock
virtualxid | [NULL] | 3/38672 | ExclusiveLock
(2 rows)
> SELECT txid_current();
txid_current
--------------
355056
(1 row)
> SELECT locktype,transactionid,virtualtransaction,mode FROM pg_locks;
locktype | transactionid | virtualtransaction | mode
---------------+---------------+--------------------+-----------------
relation | [NULL] | 3/38672 | AccessShareLock
virtualxid | [NULL] | 3/38672 | ExclusiveLock
transactionid | 355056 | 3/38672 | ExclusiveLock
(3 rows)
So now from this we can get the virtualtransaction
id shared by everything in the current transaction, and filter on that.
In other words:
SELECT
*
FROM pg_locks pl
WHERE
virtualtransaction=(
SELECT virtualtransaction FROM pg_locks
WHERE
transactionid::text = (txid_current() % (2^32)::bigint)::text
-- compare int to xid, see https://dba.stackexchange.com/a/123183/10371
AND locktype='transactionid'
LIMIT 1
)
...will return only locks relevant to the current transaction.