Il modello è (senza gestione degli errori):
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;
Non è necessario eseguire una lettura aggiuntiva della tabella #temp qui. Lo stai già facendo provando l'aggiornamento. Per proteggerti dalle condizioni di gara, fai lo stesso che proteggeresti qualsiasi blocco di due o più istruzioni che desideri isolare:lo avvolgeresti in una transazione con un livello di isolamento appropriato (probabilmente serializzabile qui, anche se tutto solo ha senso quando non stiamo parlando di una tabella #temp, poiché per definizione è serializzata).
Non sei più avanti aggiungendo un IF EXISTS
check (e dovresti aggiungere suggerimenti di blocco per renderlo sicuro / serializzabile comunque), ma potresti essere più indietro, a seconda di quante volte aggiorni le righe esistenti rispetto a inserirne di nuove. Ciò potrebbe comportare molti I/O extra.
Le persone probabilmente ti diranno di usare MERGE
(che in realtà è più operazioni dietro le quinte e deve anche essere protetto con serializzabile), ti esorto a non farlo. Spiego perché qui:
- Utilizzare Attenzione con l'istruzione MERGE di SQL Server
Per un modello a più righe (come un TVP), lo gestirei allo stesso modo, ma non esiste un modo pratico per evitare la seconda lettura come puoi con il caso a riga singola. E no, MERGE
non lo evita neanche.
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;
Bene, immagino che ci sia un modo per farlo, ma non l'ho testato a fondo:
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;
In entrambi i casi, esegui prima l'aggiornamento, altrimenti aggiornerai tutte le righe che hai appena inserito, il che sarebbe uno spreco.