Error/Fix: Cannot Insert Duplicate Key for UPSERT Code in SQLServer

Error/Fix: Cannot Insert Duplicate Key for UPSERT Code in SQLServer
Spread the love

I work with developers quite a lot. There are scenarios where data is received from various sources in an application, and asynchronously pushed to database in multiple sessions/connections.

There are situations where concurrency and transaction speed is high enough to cause below UPSERT code blocks to fail with error message like Cannot insert duplicate key row in object dbo.person with unique index 'pk_person'

Below is typical UPSERT code block I see in application code –

IMPORTANT: Usually first solution that comes to mind is using MERGE statement. But I advice to not use MERGE due to lot many bugs attached with MERGE described in this blog.

The reason for failure arises from assumption that if @@rowcount is 0 means, if would be 100% safe to insert the record with same key. But we forget the fact that “what data is visible to a transaction” depends entirely on Isolation level of the transaction.

In above statement, assuming Read Committed Snapshot Isolation for transaction, in a high concurrent fast environment, it can happen that by the time we reach to INSERT statement of UPSERT, a record with same key already got inserted through another concurrent session.

For example, for test purpose, we can introduce a delay for a minute between UPDATE & INSERT of UPSERT code block, and perform the insert in another concurrent session in between of delay.

Session 01 –

Session 02 –

If we execute query of session 02 while the query of session 01 is still running, the session 02 query would execute successfully in case of Optimistic Isolation levels like Read Committed Snapshot.

So, what are the solutions to this problem?

The first solution could be to use more aggressive isolation level or table hints like Serializable. Serializable isolation level guarantees that with this isolation level, within a transaction, the data which is already read can not be modified by other sessions. Even if the exact keys are not present in table, the entire possible key range would be exclusively locked until the end of current transaction.

So a sample code with this solution would look like below –

Key problem with above solution is, there could be heavy blocking on table if ratio of new records is higher. These new record insertions would block each other even when key column values are different. This happens because the entire Key Range is locked in this isolation level.

Another solution, which is not so know,n is using sp_getapplock and sp_releaseapplock. But this solution demands that insert/update to tables happen within defined set of procedures, or from known application sources only. This is for the fact that this is a code change that has to be applied on every INSERT OR UPDATE on table.

Below is a sample code to this second solution –

Key benefit of above second solution is that locking is on virtual resouce that can be customized by developer, and is very granular. So DML operation on same table on different key values does not block each other for UPSERT operation while still preventing DML on same key values.

I hope this will be useful to developers trying for a reliable solution of UPSERT in SQL Server.

1 Comment

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.