I had always used something similar to the following to achieve it:
INSERT INTO TheTable SELECT @primaryKey, @value1, @value2 WHERE NOT EXISTS (SELECT NULL FROM TheTable WHERE PrimaryKey = @primaryKey)
…but once under load, a primary key violation occurred. This is the only statement which inserts into this table at all. So does this mean that the above statement is not atomic?
The problem is that this is almost impossible to recreate at will.
Perhaps I could change it to the something like the following:
INSERT INTO TheTable WITH (HOLDLOCK, UPDLOCK, ROWLOCK) SELECT @primaryKey, @value1, @value2 WHERE NOT EXISTS (SELECT NULL FROM TheTable WITH (HOLDLOCK, UPDLOCK, ROWLOCK) WHERE PrimaryKey = @primaryKey)
Although, maybe I’m using the wrong locks or using too much locking or something.
I have seen other questions on stackoverflow.com where answers are suggesting a “IF (SELECT COUNT(*) … INSERT” etc., but I was always under the (perhaps incorrect) assumption that a single SQL statement would be atomic.
Does anyone have any ideas?
Advertisement
Answer
What about the “JFDI” pattern?
BEGIN TRY INSERT etc END TRY BEGIN CATCH IF ERROR_NUMBER() <> 2627 RAISERROR etc END CATCH
Seriously, this is quickest and the most concurrent without locks, especially at high volumes. What if the UPDLOCK is escalated and the whole table is locked?
Lesson 4: When developing the upsert proc prior to tuning the indexes, I first trusted that the
If Exists(Select…)
line would fire for any item and would prohibit duplicates. Nada. In a short time there were thousands of duplicates because the same item would hit the upsert at the same millisecond and both transactions would see a not exists and perform the insert. After much testing the solution was to use the unique index, catch the error, and retry allowing the transaction to see the row and perform an update instead an insert.