BUG #9301: INSERT WHERE NOT EXISTS on table with UNIQUE constraint in concurrent SERIALIZABLE transactions

From: mscott(at)apple(dot)com
To: pgsql-bugs(at)postgresql(dot)org
Subject: BUG #9301: INSERT WHERE NOT EXISTS on table with UNIQUE constraint in concurrent SERIALIZABLE transactions
Date: 2014-02-21 00:20:01
Message-ID: 20140221002001.29130.27157@wrigleys.postgresql.org
Views: Raw Message | Whole Thread | Download mbox | Resend email
Thread:
Lists: pgsql-bugs

The following bug has been logged on the website:

Bug reference: 9301
Logged by: Scott Marcy
Email address: mscott(at)apple(dot)com
PostgreSQL version: 9.3.2
Operating system: Mac OS X 10.9, CentOS 6.5
Description:

------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------
-- The following seems to violate the fundamental guarantee of ISOLATION
LEVEL SERIALIZABLE in that the two
-- transactions below do not behave the same as if they were run serially.
Code that checks for
-- serialization failures obviously doesn't catch this problem and there is
no good workaround (other than
-- removing the UNIQUE constraint) as you get the same behavior if you use a
plpgsql function and run the
-- SELECT query separately.
------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------

-----------
-- Setup --
-----------
CREATE TABLE test (
key integer UNIQUE,
val text
);

CREATE OR REPLACE FUNCTION insert_unique(k integer, v text) RETURNS void
LANGUAGE SQL AS $$
INSERT INTO test (key, val) SELECT k, v WHERE NOT EXISTS (SELECT key FROM
test WHERE key = k);
$$;

----------
-- Test --
----------

---------------------------
-- On psql Connection #1 --
---------------------------
\set VERBOSITY verbose
BEGIN ISOLATION LEVEL SERIALIZABLE;
SELECT insert_unique(1, '1');

---------------------------
-- On psql Connection #2 --
---------------------------
\set VERBOSITY verbose
BEGIN ISOLATION LEVEL SERIALIZABLE;
SELECT insert_unique(1, '2');

-- (Connection #2 waits here for #1)

---------------------------
-- On psql Connection #1 --
---------------------------
COMMMIT;

-- Connection #2 blows up:
-- ERROR: 23505: duplicate key value violates unique constraint
"test_key_key"
-- DETAIL: Key (key)=(1) already exists.
-- CONTEXT: SQL function "insert_unique" statement 1
-- LOCATION: _bt_check_unique, nbtinsert.c:398

-- Adding a "LOCK TABLE test IN ACCESS EXCLUSIVE MODE;" at the top of the
function doesn't help if you've performed
-- any queries prior to using this function.
-- Adding a "FOR UPDATE" to the WNE subquery does not help
-- Removing the UNIQUE constraint avoids the duplicate key error and
properly causes a serialization failure on
-- Connection #2's transaction.

-- It appears that the UNIQUE INDEX is not snapshotted at the start of a
transaction the same way the data table
-- is. The row inserted by Connection #1 is obviosuly visible in the index
in the transaction on Connection #2.

---------------------------
-- On psql Connection #2 --
---------------------------
ROLLBACK;
ALTER TABLE test DROP CONSTRAINT test_key_key;
DELETE FROM test;

-- Now repeat the test.

-- Output on Connection #2 (might need to 'COMMIT' on Connection #2) is as
expected:
-- ERROR: 40001: could not serialize access due to read/write dependencies
among transactions
-- DETAIL: Reason code: Canceled on identification as a pivot, during
commit attempt.
-- HINT: The transaction might succeed if retried.
-- LOCATION: PreCommit_CheckForSerializationFailure, predicate.c:4651

Responses

Browse pgsql-bugs by date

  From Date Subject
Next Message Haribabu Kommi 2014-02-21 03:26:27 Re: Is there a Support Platforms document more current that this?
Previous Message fburgess 2014-02-20 22:39:54 Re: Connection errors in PostgreSQL 9.3.2