HiveBrain v1.2.0
Get Started
← Back to all entries
gotchasqlMinor

Why does upsert result in a deadlock?

Submitted by: @import:stackexchange-dba··
0
Viewed 0 times
resultwhydeadlockupsertdoes

Problem

I'm doing the below upsert statement, which I need to have run multiple times in a short timeframe. I wrote this statement based on examples from Aaron Bertrand's Please stop using this UPSERT anti-pattern post on SQLPerformance.com. The majority of my UPSERTS are INSERTS, so I used that example from his post.

BEGIN TRANSACTION;

INSERT AggregationMeasurement (ValueFloat, Date, AggregationConfigurationId)
SELECT @ValueFloat, @Date, @AggregationConfigurationId
WHERE NOT EXISTS
(
    SELECT 1 FROM dbo.AggregationMeasurement WITH (UPDLOCK, SERIALIZABLE)
    WHERE Date = @Date AND AggregationConfigurationId = @AggregationConfigurationId
);

IF @@ROWCOUNT = 0
BEGIN
UPDATE AggregationMeasurement SET 
    ValueFloat = @ValueFloat
WHERE Date = @Date AND AggregationConfigurationId = @AggregationConfigurationId;
END

COMMIT TRANSACTION;


However, every time this results in several deadlocks, as shown here:

```








INSERT AggregationMeasurement (ValueFloat, Date, AggregationConfigurationId)
SELECT @ValueFloat, @Date, @AggregationConfigurationId
WHERE NOT EXISTS
(
SELECT 1 FROM dbo.AggregationMeasurement WITH (UPDLOCK, SERIALIZABLE)
WHERE Date = @Date AND AggregationConfigurationId = @AggregationConfigurationId


EXEC sp_UpsertAggregateMeasurement @ValueFloat = @ValueFloat_tmp, @Date = @Date_tmp, @AggregationConfigurationId = @AggregationConfigurationI



Proc [Database Id = 5 Object Id = 1554104577]





INSERT AggregationMeasurement (ValueFloat, Date, AggregationConfigurationId)
SELECT @ValueFloat, @Date, @AggregationConfigurationId
WHERE NOT EXISTS

Solution

The first thing you want to do is make sure you have an index that meets the needs of your WHERE clause. In this case, it would need key columns on Date and AggregationConfigurationId. Doing this will ensure the locks are held for as brief a period as possible.

After that, if your UPSERT is predominately going to be UPDATES instead of INSERTS, you'd want revise your UPSERT statement to the below statement. Otherwise, the statement you are using looks good.

BEGIN TRANSACTION;

UPDATE AggregationMeasurement WITH (UPDLOCK, SERIALIZABLE)
SET ValueFloat = @ValueFloat
WHERE Date = @Date
    AND AggregationConfigurationId = @AggregationConfigurationId;

IF @@ROWCOUNT = 0
BEGIN
    INSERT AggregationMeasurement
        (ValueFloat, Date, AggregationConfigurationId)
    VALUES (@ValueFloat, @Date, @AggregationConfigurationId)
END

COMMIT TRANSACTION;

Code Snippets

BEGIN TRANSACTION;

UPDATE AggregationMeasurement WITH (UPDLOCK, SERIALIZABLE)
SET ValueFloat = @ValueFloat
WHERE Date = @Date
    AND AggregationConfigurationId = @AggregationConfigurationId;

IF @@ROWCOUNT = 0
BEGIN
    INSERT AggregationMeasurement
        (ValueFloat, Date, AggregationConfigurationId)
    VALUES (@ValueFloat, @Date, @AggregationConfigurationId)
END

COMMIT TRANSACTION;

Context

StackExchange Database Administrators Q#305233, answer score: 4

Revisions (0)

No revisions yet.