Mønsteret er (uden fejlhåndtering):
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;
Du behøver ikke at udføre en yderligere læsning af #temp-tabellen her. Det gør du allerede ved at prøve opdateringen. For at beskytte mod raceforhold gør du det samme, som du ville beskytte enhver blok af to eller flere udsagn, som du ønsker at isolere:du vil pakke det ind i en transaktion med et passende isolationsniveau (sandsynligvis kan serialiseres her, selvom det kun alle giver mening, når vi ikke taler om en #temp-tabel, da den per definition er serialiseret).
Du er ikke længere fremme ved at tilføje en IF EXISTS
check (og du bliver nødt til at tilføje låsetip for at gøre det sikkert / serialiserbart alligevel), men du kan være længere bagud, afhængigt af hvor mange gange du opdaterer eksisterende rækker vs. indsæt nye. Det kan tilføje op til en masse ekstra I/O.
Folk vil sandsynligvis fortælle dig, at du skal bruge MERGE
(som faktisk er flere operationer bag kulisserne og også skal beskyttes med serialiserbar), opfordrer jeg dig til at lade være. Jeg forklarer hvorfor her:
- Vær forsigtig med SQL Servers MERGE-erklæring
For et mønster med flere rækker (som en TVP), ville jeg håndtere dette på nogenlunde samme måde, men der er ikke en praktisk måde at undgå den anden læsning, som du kan med enkeltrækkets sag. Og nej, MERGE
undgår det heller ikke.
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;
Nå, jeg gætter på, at der er en måde at gøre det på, men jeg har ikke testet dette grundigt:
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;
I begge tilfælde udfører du opdateringen først, ellers opdaterer du alle de rækker, du lige har indsat, hvilket ville være spild.