I have a simple table counter which is as below.
CREATE TABLE counter
(
id text NOT NULL,
total integer NOT NULL,
CONSTRAINT pk_id PRIMARY KEY (id)
)
WITH (
OIDS=FALSE
);
The counter.id has a fixed set of 20 values and I have manually set the initial total to 0 for all the 20 counter.id. In one of the stored procedures, I added the following line
UPDATE counter SET total = total + 1 WHERE id = 'SomeID'; and now I see a large number of could not serialize access due to read/write dependencies among transactions postgres exceptions. If I comment that line out, the problem goes away. The table counter is not being updated/read anywhere else concurrently but this line.
I am using an ISOLATION level SERIALIZABLE in my transactions. The data access layer consists of Java Spring JDBC. I tried the following two approaches to resolve this issue.
- Use a
LOCK counter in ACCESS EXCLUSIVE MODE;before callingUPDATE counter. - Use PERFORM pg_advisory_xact_lock(1); right before calling
UPDATE counter.
I am astonished that both the approaches did not solve the problem. From the documentation, the LOCK should give one thread an exclusive access to the table counter which should have prevented serializable exceptions. But it does not appear to work.
Any suggestions as to what I am doing wrong here?
UPDATE: So here is my attempt to reproduce the problem in a bit more simplified setting. I have have a single stored procedure which is as below.
CREATE OR REPLACE FUNCTION public.testinsert() RETURNS void AS
$BODY$
BEGIN
LOCK counter IN ACCESS EXCLUSIVE MODE;
INSERT INTO counter("id", "total")
VALUES('SomeID', 0) ON CONFLICT(id)
DO UPDATE SET total = counter.total + 1 where counter.id = 'SomeID';
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION public.testinsert()
Now I attempt the following in two separate psql consoles.
Console1: begin transaction isolation level serializable;
Console2: begin transaction isolation level serializable;
Console1: select testInsert();
Console2: select testInsert();
Console1: commit;
At this point Console2 throws an exception could not serialize access due to concurrent update. This clearly tells me that the lock counter does not work when placed inside the stored procedure. Any ideas why?
If I try a variation of this with Console1 and Console2 both doing a lock counter right after begin transaction, followed by calling the stored procedure, the code works just fine as Console2 now waits on the lock.
I have tried replacing the lock counter with PERFORM pg_advisory_xact_lock(1) and encountered similar problems.
counteris not accessed anywhere else but exactly at one location. All methods are marked as VOLATILE.