0

I haven't been able to find an answer to this. Suppose I have the following table/query:

The table:

create table ##table
(
   column1 int,
   column2 nvarchar(max)
)

The query (in a real life scenario the condition will be more complex):

declare @shouldInsert bit
set @shouldInsert = case when exists(
    select * 
    from ##table
    where column2 = 'test') then 1 else 0 end

--Exaggerating a possible delay:
waitfor delay '00:00:10'

if(@shouldInsert = 0)
   insert into ##table
   values(1, 'test')

If I run this query twice simultaneously then it's liable to insert duplicate records (enforsing a unique constraint is out of the question because the real-life condition is more involved than the mere "column1" uniqueness across the table)

I see two possible solutions:

  1. I run both concurrent transactions in serializable mode, but it will create a deadlock (first a shared lock in select then an x-lock in insert - deadlock).

  2. In the select statement I use the query hints with(update, tablock) which will effectively x-lock the entire table, but it will prevent other transactions from reading data (something I'd like to avoid)

Which is more acceptable? Is there a third solution?

Thanks.

3
  • Can't you just use LEFT JOIN insert into ##table SELECT t.col1, t.col2 FROM (SELECT 1 as col1, 'test' as col2) t LEFT JOIN ##table temp ON t.col1 = temp.col1 WHERE temp.col1 IS NULL? Commented Sep 25, 2015 at 5:40
  • Did you consider combining the shouldInsert test and the actual insert into a single SQL statement. Something along the lines of "insert into ##table (select 1,'test' from dual where not exists ..." Commented Sep 25, 2015 at 6:40
  • I would opt for the second solution, but using the INSERT...SELECT...WHERE NOT EXISTS syntax with the locking hint in the subquery. I wouldn't expect concurrency to be an issue unless you have a very high insert rate, assuming a unique index on column2. Commented Sep 25, 2015 at 11:53

3 Answers 3

1

If you can, you should put a UNIQUE constraint (or index) on whatever column(s) it is that is defining the uniqueness.

With this, you might still get the "OK, doesn't exist yet" response for your initial check for two separate processes - but one of the two will be first and get his row inserted, while the second will get a "unique constraint violated" exception back from the database.

Sign up to request clarification or add additional context in comments.

Comments

0

Regardless how "involved" your "real-life condition" is you have two options: enforce UNIQUE or deal with multiple records. Any work-around will likely be fragile.

For example your delay hack is pretty useless if you need to add another DB server or overwhelming load slows down the execution of individual threads

One of the ways you could allow for multiple copies of a should-be-unique value is to create another table that can act as a queue and doesn't enforce uniqueness and a serial worker to dequeue it. Or change the data structure to allow for 1-to-many and pick the first one when querying. Still a hack but at least not terribly "creative" and it can't break

Comments

0
declare @shouldInsert bit
set @shouldInsert = case when exists(
    select * 
    from ##table
    where column2 = 'test') then 1 else 0 end

--Exaggerating a possible delay:
waitfor delay '00:00:10'

truncate table #temp

if(@shouldInsert = 0)
   insert into #temp
   values(1, 'test')

--if records is not available in ##table then data will be inserted from #temp table to ##table

insert into ##table
select * from #temp
except
select * from ##table

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.