Thread: How to prevent duplicate key error when two processes do DELETE/INSERT simultaneously?
How to prevent duplicate key error when two processes do DELETE/INSERT simultaneously?
From
Brodie Thiesfield
Date:
Hi, I've got a problem with a PG client that I'm not sure how to fix. Essentially, I have two processes connecting to a single PG database and simultaneously issuing the following statements: BEGIN; SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; DELETE FROM licence_properties WHERE key = xxx; INSERT INTO licence_properties ... values with key = xxx; COMMIT One of these processes is getting to the INSERT and failing with duplicate key error. ERROR: duplicate key value violates unique constraint The DELETE should prevent this duplicate key error from occurring. I thought that the ISOLATION LEVEL SERIALIZABLE would fix this problem (being that the second process can see the INSERT from the first process after it has done the DELETE), but it doesn't. I am obviously going about this the wrong way. The database layer is implemented for a number of different servers and so I was trying to keep it simple. However, perhaps this is something that I can't simplify. Should I do SELECT FOR UPDATE and then either an INSERT or UPDATE? I would be very appreciative if someone more knowledgeable would point me to the correct way of doing this? The full PG log of the two processes follows (in case it is useful). Regards, Brodie log_line_prefix = '%m %p %x %v ' (timestamp, process ID, transaction ID, virtual transaction ID) logs are stable sorted by process ID then timestamp. Process 1: 2009-07-29 23:01:01.218 JST 6108 0 3/65 LOG: statement: BEGIN;SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; 2009-07-29 23:01:01.218 JST 6108 0 3/65 LOG: statement: SAVEPOINT _EXEC_SVP_03741378 2009-07-29 23:01:01.218 JST 6108 0 3/65 LOG: statement: SELECT lid FROM licences WHERE groupid = E'' AND userid = E'test'; 2009-07-29 23:01:01.218 JST 6108 0 3/65 LOG: statement: RELEASE _EXEC_SVP_03741378 2009-07-29 23:01:01.218 JST 6108 0 3/65 LOG: statement: SAVEPOINT _EXEC_SVP_03740350 2009-07-29 23:01:01.218 JST 6108 0 3/65 LOG: statement: DELETE FROM licence_properties WHERE lid = '114' AND pkey = E'mytestprop' AND sessionid IN (0, '0') AND readonly IN (0, 0); 2009-07-29 23:01:01.218 JST 6108 0 3/65 LOG: statement: RELEASE _EXEC_SVP_03740350 2009-07-29 23:01:01.218 JST 6108 0 3/65 LOG: statement: SAVEPOINT _EXEC_SVP_03742768 2009-07-29 23:01:01.218 JST 6108 0 3/65 LOG: statement: INSERT INTO licence_properties (lid, sessionid, pkey, value, readonly) VALUES ('114', '0', E'mytestprop', E'update2', 0); 2009-07-29 23:01:01.218 JST 6108 453328 3/65 LOG: statement: RELEASE _EXEC_SVP_03742768 2009-07-29 23:01:01.218 JST 6108 453328 3/65 LOG: statement: SAVEPOINT _EXEC_SVP_03740868 2009-07-29 23:01:01.218 JST 6108 453328 3/65 LOG: statement: UPDATE licences SET revision = revision + 1 WHERE groupid = E'' AND userid = E'test'; 2009-07-29 23:01:01.218 JST 6108 453328 3/65 LOG: statement: RELEASE _EXEC_SVP_03740868 2009-07-29 23:01:01.218 JST 6108 453328 3/65 LOG: statement: COMMIT Process 2: 2009-07-29 23:01:01.218 JST 5460 0 2/47 LOG: statement: BEGIN;SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; 2009-07-29 23:01:01.218 JST 5460 0 2/47 LOG: statement: SAVEPOINT _EXEC_SVP_03741378 2009-07-29 23:01:01.218 JST 5460 0 2/47 LOG: statement: SELECT lid FROM licences WHERE groupid = E'' AND userid = E'test'; 2009-07-29 23:01:01.218 JST 5460 0 2/47 LOG: statement: RELEASE _EXEC_SVP_03741378 2009-07-29 23:01:01.218 JST 5460 0 2/47 LOG: statement: SAVEPOINT _EXEC_SVP_03740350 2009-07-29 23:01:01.218 JST 5460 0 2/47 LOG: statement: DELETE FROM licence_properties WHERE lid = '114' AND pkey = E'mytestprop' AND sessionid IN (0, '0') AND readonly IN (0, 0); 2009-07-29 23:01:01.218 JST 5460 0 2/47 LOG: statement: RELEASE _EXEC_SVP_03740350 2009-07-29 23:01:01.218 JST 5460 0 2/47 LOG: statement: SAVEPOINT _EXEC_SVP_03742768 2009-07-29 23:01:01.218 JST 5460 0 2/47 LOG: statement: INSERT INTO licence_properties (lid, sessionid, pkey, value, readonly) VALUES ('114', '0', E'mytestprop', E'update2', 0); 2009-07-29 23:01:01.218 JST 5460 453330 2/47 ERROR: duplicate key value violates unique constraint "pk_lic_prop" 2009-07-29 23:01:01.218 JST 5460 453330 2/47 STATEMENT: INSERT INTO licence_properties (lid, sessionid, pkey, value, readonly) VALUES ('114', '0', E'mytestprop', E'update2', 0); 2009-07-29 23:01:01.218 JST 5460 453330 2/47 LOG: statement: ROLLBACK to _EXEC_SVP_03742768 2009-07-29 23:01:01.218 JST 5460 453330 2/47 LOG: statement: RELEASE _EXEC_SVP_03742768 2009-07-29 23:01:01.218 JST 5460 453330 2/47 LOG: statement: ROLLBACK
Re: How to prevent duplicate key error when two processes do DELETE/INSERT simultaneously?
From
Tom Lane
Date:
Brodie Thiesfield <brofield+pgsql@gmail.com> writes: > Essentially, I have two processes connecting to a single PG database > and simultaneously issuing the following statements: > BEGIN; > SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; > DELETE FROM licence_properties WHERE key = xxx; > INSERT INTO licence_properties ... values with key = xxx; > COMMIT You mean they both want to insert the same key? > One of these processes is getting to the INSERT and failing with > duplicate key error. > ERROR: duplicate key value violates unique constraint If they both insert the same key, this is what *must* happen. Surely you don't expect both to succeed, or one to fail and not tell you. > The DELETE should prevent this duplicate key error from occurring. I > thought that the ISOLATION LEVEL SERIALIZABLE would fix this problem > (being that the second process can see the INSERT from the first > process after it has done the DELETE), but it doesn't. I think you've got the effects of SERIALIZABLE backward, but in any case SERIALIZABLE does not affect uniqueness checks. Unique is unique. regards, tom lane
Re: How to prevent duplicate key error when two processes do DELETE/INSERT simultaneously?
From
Brodie Thiesfield
Date:
On Thu, Jul 30, 2009 at 12:23 AM, Tom Lane<tgl@sss.pgh.pa.us> wrote: > Brodie Thiesfield <brofield+pgsql@gmail.com> writes: >> Essentially, I have two processes connecting to a single PG database >> and simultaneously issuing the following statements: > >> BEGIN; >> SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; >> DELETE FROM licence_properties WHERE key = xxx; >> INSERT INTO licence_properties ... values with key = xxx; >> COMMIT > > You mean they both want to insert the same key? Yes. There are two processes working independently on the same data. They are both trying to work around the lack of INSERT OR REPLACE by doing a DELETE/INSERT. I was hoping that this could be somehow done as an atomic action. >> One of these processes is getting to the INSERT and failing with >> duplicate key error. >> ERROR: duplicate key value violates unique constraint > > If they both insert the same key, this is what *must* happen. Surely > you don't expect both to succeed, or one to fail and not tell you. Yes, it appears my grasp of transaction isolation apparently isn't so firm. So, the delete/insert combination cannot be made atomic and transaction isolation is only for read and not update. I was hoping that the updates would be serialized and so both would succeed with only one being the eventual winner. On further investigation, since the logic requires the delete to be made first to get rid of other possible rows, so I'll go with: DELETE (if supported) INSERT OR REPLACE (otherwise) INSERT, if duplicate key, UPDATE Regards, Brodie
Re: How to prevent duplicate key error when two processes do DELETE/INSERT simultaneously?
From
Alex Hunsaker
Date:
On Wed, Jul 29, 2009 at 19:53, Brodie Thiesfield<brofield+pgsql@gmail.com> wrote: > On further investigation, since the logic requires the delete to be > made first to get rid of other possible rows, so I'll go with: > > DELETE > (if supported) INSERT OR REPLACE > (otherwise) INSERT, if duplicate key, UPDATE > You can use plpgsql to emulate insert or replace (I think there are some comments on emulating mysql specifically in the commented versions of the docs somewhere... but thats up to you to find em) see http://www.postgresql.org/docs/8.4/static/plpgsql-control-structures.html#PLPGSQL-ERROR-TRAPPING example 38-1