One of previous articles showed how easily we might get duplicate rows if we use following approach for UPSERT:
UPDATE MyTable
SET Col1 = NewValue
WHERE ID = @param1
ELSE
INSERT MyTable(ID, Col1)
VALUES (@param1, @param2)
This time I decided to do other tests but without WAITFOR. I’ve used following table (the same table) without any non-clustered indexes (like IUN_Customer_Email) and with just two rows:
GO
DBCC FREEPROCCACHE;
GO
USE TestUPSERT
GO
IF OBJECT_ID(N'dbo.Customer') IS NOT NULL
DROP TABLE dbo.Customer;
CREATE TABLE dbo.Customer (
CustomerID INT IDENTITY(1,1) PRIMARY KEY,
Email NVARCHAR(100) NOT NULL,
Note NVARCHAR(50) NULL,
OrdersCount INT NOT NULL DEFAULT (1)
);
GO
INSERT dbo.Customer (Email) VALUES (N'[email protected]');
INSERT dbo.Customer (Email) VALUES (N'[email protected]');
GO
For my test I’ve used three scripts which try to insert or updated multiple times three emails: [email protected] (SQLQuery1.sql), [email protected] (SQLQuery2.sql) and [email protected] (SQLQuery3.sql). These files are available in D:\SqlOstressInput folder.
Content of SQLQuery{1|2|3}.sql script:
DECLARE @Email NVARCHAR(100);
SET @Email = '[email protected]'; – or '[email protected]' or '[email protected]'
– End of Parameters
BEGIN TRANSACTION;
IF EXISTS(SELECT * FROM dbo.Customer WHERE Email = @Email)
BEGIN
UPDATE dbo.Customer
SET OrdersCount = OrdersCount + 1
WHERE Email = @Email;
END
ELSE
BEGIN
INSERT dbo.Customer(Email)
VALUES (@Email) – OrdersCount = Default 1
END
COMMIT;
To run these scripts multiple times (100 times) using 10 concurrent connections I’ve used ostress.exe which is included into RML utilities for SQL Server. Before test I’ve used DBCC DROPCLEANBUFFERS and for ostress.exe I’ve used following arguments:
ostress -S(local)\SQL2012 -E -dTestUPSERT -o"d:\SqlOstressOutput" -i"d:\SqlOstressInput\SQLQuery*.sql" -n10 -r100 -q
After few tries I succeeded to get duplicate rows (sample output, you might get different results) and for some emails ([email protected] and [email protected]) the OrderCount is wrong:
———– —– —- ———–
1 [email protected] NULL 1
2 [email protected] NULL 1001
3 [email protected] NULL 999
4 [email protected] NULL 999
5 [email protected] NULL 996
6 [email protected] NULL 996
7 [email protected] NULL 996
8 [email protected] NULL 996
9 [email protected] NULL 996
Conclusion: This approach for UPSERT (without any unique indexes and table hints or without serializable transaction isolation level) isn’t safe.
Note: for this test I used
Oct 19 2012 13:38:57
Copyright (c) Microsoft Corporation
Developer Edition (64-bit) on Windows NT 6.1 <X64> (Build 7601: Service Pack 1)
Transaction isolation level was READ COMMITTED (default).