Re: Working around spurious unique constraint errors due to SERIALIZABLE bug

From: "Albe Laurenz" <laurenz(dot)albe(at)wien(dot)gv(dot)at>
To: "Florian Weimer *EXTERN*" <fweimer(at)bfk(dot)de>, <pgsql-general(at)postgresql(dot)org>
Subject: Re: Working around spurious unique constraint errors due to SERIALIZABLE bug
Date: 2009-07-16 13:15:56
Message-ID: D960CB61B694CF459DCFB4B0128514C203937E44@exadv11.host.magwien.gv.at
Views: Raw Message | Whole Thread | Download mbox | Resend email
Thread:
Lists: pgsql-general

Florian Weimer wrote:
> SERIALIZABLE isolation level doesn't really conform to the spec
> because it doesn't deal with phantoms. The only case I've come across
> where this actually matters is when you're implementing some sort of
> "insert into table if not yet present" operation. This will typically
> result in a unique constraint violation.[*]
>
> Usually, constraint violations are programming errors, but not this
> one. It's more like a detected deadlock. Is there a way to tell this
> type of constraint violation from other types, so that the transaction
> can be restarted automatically (as if there was a deadlock)?
> Theoretically, PostgreSQL should detect that the conflicting row
> wasn't there when the snapshot for the transaction was taken, and
> somehow export this piece of information, but I'm not sure if it's
> available to the client.
>
> [*] One way to work around this is to batch inserts and eventually
> perform them in a background task which doesn't run in parallel, but
> this approach isn't always possible.

Let me construct an example:

CREATE TABLE a (id integer PRIMARY KEY);

CREATE FUNCTION ins(i integer) RETURNS boolean
LANGUAGE plpgsql STRICT AS
$$DECLARE
i2 integer;
BEGIN
SELECT COUNT(id) INTO i2 FROM a WHERE id = i;
IF i2 = 0 THEN
/* This INSERT will never throw an exception if the
transactions are truly serialized */
INSERT INTO a (id) VALUES (i);
RETURN TRUE;
ELSE
RETURN FALSE;
END IF;
END;$$;

Now sessions A and B do the following:

A: START TRANSACTION ISOLATION LEVEL SERIALIZABLE;
A: SELECT * FROM a;
id
----
(0 rows)

B: START TRANSACTION ISOLATION LEVEL SERIALIZABLE;
B: SELECT * FROM a;
id
----
(0 rows)

B: SELECT ins(1);
ins
-----
t
(1 row)

A: SELECT ins(1);
Session A is blocked by B's exclusive lock.

B: COMMIT;

Now A gets:
ERROR: duplicate key value violates unique constraint "a_pkey"
CONTEXT: SQL statement "INSERT INTO a (id) VALUES ( $1 )"
PL/pgSQL function "ins" line 1 at SQL statement

This is what you are talking about, right?

I am not sure what exactly you mean by retrying the transaction in
Session A. Even on a second try A would not be able to insert the
duplicate key. But at least there would not be an error:

A: ROLLBACK;
A: SELECT ins(1);
ins
-----
f
(1 row)

The best way to work around a problem like this is to write
code that does not assume true serializability, for example:

BEGIN
INSERT INTO a (id) VALUES (i);
RETURN TRUE;
EXCEPTION
WHEN unique_violation THEN
RETURN FALSE;
END;

Maybe my example is too simple, but it should work similar to this whenever
error conditions are involved.

Other problems will be more tricky (I am thinking of the example I
constructed for http://archives.postgresql.org/pgsql-hackers/2009-05/msg00316.php).
I don't think that there is a "king's way" to cope with all possible problems.

Yours,
Laurenz Albe

In response to

Responses

Browse pgsql-general by date

  From Date Subject
Next Message Ms swati chande 2009-07-16 13:21:24 Re: Using the geqo
Previous Message Bill Moran 2009-07-16 13:05:58 Re: suggestion: log_statement = sample