Шаблон (без обработки ошибок):
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRANSACTION;
UPDATE #TProductSales SET StockQty = @StockQty, ETA1 = @ETA1
WHERE ProductID = @ProductID;
IF @@ROWCOUNT = 0
BEGIN
INSERT #TProductSales(ProductID, StockQTY, ETA1)
VALUES(@ProductID, @StockQTY, @ETA1);
END
COMMIT TRANSACTION;
Вам не нужно выполнять дополнительное чтение таблицы #temp здесь.Вы уже делаете это, пытаясь обновить.Для защиты от состояния гонки вы делаете то же самое, что и любой блок из двух или более операторов, которые вы хотите изолировать: вы заключаете их в транзакцию с соответствующим уровнем изоляции (вероятно, здесь можно сериализовать, хотя это все толькоимеет смысл, когда мы не говорим о таблице #temp, поскольку она по определению сериализована).
Вы не продвинетесь дальше, добавив проверку IF EXISTS
(и , вам потребуетсядобавить подсказки о блокировке, чтобы сделать это безопасным / сериализуемым в любом случае ), но вы можете отстать, в зависимости от того, сколько раз вы обновляете существующие строки и вставляете новые.Это может привести к большому количеству дополнительных операций ввода-вывода.
Люди, вероятно, скажут вам использовать MERGE
(что на самом деле является множеством скрытых операций, и также необходимо защитить с помощью сериализуемых), я призываю вас не делать этого.Выкладываю, почему здесь:
Для многорядного шаблона (например, TVP) ябудет обрабатывать это точно так же, но не существует практического способа избежать второго чтения, как вы можете в случае однострочного.И нет, MERGE
тоже не избегает этого.
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRANSACTION;
UPDATE t SET t.col = tvp.col
FROM dbo.TargetTable AS t
INNER JOIN @TVP AS tvp
ON t.ProductID = tvp.ProductID;
INSERT dbo.TargetTable(ProductID, othercols)
SELECT ProductID, othercols
FROM @TVP AS tvp
WHERE NOT EXISTS
(
SELECT 1 FROM dbo.TargetTable
WHERE ProductID = tvp.ProductID
);
COMMIT TRANSACTION;
Ну, я думаю, что есть способ сделать это, но я не проверял это полностью:
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRANSACTION;
DECLARE @exist TABLE(ProductID int PRIMARY KEY);
UPDATE t SET t.col = tvp.col
OUTPUT deleted.ProductID INTO @exist
FROM dbo.TargetTable AS t
INNER JOIN @tvp AS tvp
ON t.ProductID = tvp.ProductID;
INSERT dbo.TargetTable(ProductID, othercols)
SELECT ProductID, othercols
FROM @tvp AS t
WHERE NOT EXISTS
(
SELECT 1 FROM @exist
WHERE ProductID = t.ProductID
);
COMMIT TRANSACTION;
В любом случае вы сначала выполняете обновление, в противном случае вы обновите все только что вставленные строки, что было бы бесполезно.