Given:
customer[id BIGINT AUTO_INCREMENT PRIMARY KEY, email VARCHAR(30), count INT]
I'd like to execute the following atomically: Update the customer if he already exists; otherwise, insert a new customer.
In theory this sounds like a perfect fit for SQL-MERGE but the database I am using doesn't support MERGE with AUTO_INCREMENT columns.
https://stackoverflow.com/a/1727788/14731 seems to indicate that if you execute a query or update statement against a non-existent row, the database will lock the index thereby preventing concurrent inserts.
Is this behavior guaranteed by the SQL standard? Are there any databases that do not behave this way?
UPDATE: Sorry, I should have mentioned this earlier: the solution must use READ_COMMITTED transaction isolation unless that is impossible in which case I will accept the use of SERIALIZABLE.
Answering my own question since there seems to be a lot of confusion around the topic. It seems that:
is open to race-conditions (see Only inserting a row if it's not already there). From what I've been able to gather, the only portable solution to this problem:
insert
a new row. You must catch the error that will occur if the row already exists.insert
from the previous step).update
if needed orselect
its primary key.This question is asked about once a week on SO, and the answers are almost invariably wrong.
Here's the right one.
If you like, you can insert a count of 1 and skip the
update
if the inserted rowcount -- however expressed in your DBMS -- returns 1.The above syntax is absolutely standard and makes no assumption about locking mechanisms or isolation levels. If it doesn't work, your DBMS is broken.
Many people are under the mistaken impression that the
select
executes "first" and thus introduces a race condition. No: thatselect
is part of theinsert
. The insert is atomic. There is no race.Use Russell Fox's code but use
SERIALIZABLE
isolation. This will take a range lock so that the non-existing row is logically locked (together with all other non-existing rows in the surrounding key range).So it looks like this:
Most code taken from his answer, but fixed to provided mutual exclusion semantics.