Het patroon is (zonder foutafhandeling):
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;
U hoeft hier geen extra lezing van de #temp-tabel uit te voeren. Dat doe je al door de update te proberen. Om te beschermen tegen race-omstandigheden, doe je hetzelfde als je elk blok van twee of meer verklaringen die je wilt isoleren zou beschermen:je zou het in een transactie verpakken met een geschikt isolatieniveau (waarschijnlijk serialiseerbaar hier, hoewel dat allemaal alleen is logisch als we het niet hebben over een #temp-tabel, want die is per definitie geserialiseerd).
Je bent geen stap verder door een IF EXISTS
. toe te voegen check (en je zou vergrendelingshints moeten toevoegen om dat toch veilig / serialiseerbaar te maken), maar je zou verder achter kunnen lopen, afhankelijk van hoe vaak je bestaande rijen bijwerkt versus nieuwe invoegt. Dat kan een hoop extra I/O opleveren.
Mensen zullen je waarschijnlijk vertellen om MERGE
te gebruiken (wat eigenlijk meerdere operaties achter de schermen zijn, en die ook moeten worden beschermd met serializable), ik dring er bij je op aan dat niet te doen. Ik leg hier uit waarom:
- Wees voorzichtig met de MERGE-instructie van SQL Server
Voor een patroon met meerdere rijen (zoals een TVP), zou ik dit op dezelfde manier aanpakken, maar er is geen praktische manier om de tweede lezing te vermijden, zoals je kunt met het geval met één rij. En nee, MERGE
ontwijkt het ook niet.
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;
Nou, ik denk dat er een manier is om het te doen, maar ik heb dit niet grondig getest:
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 beide gevallen voert u eerst de update uit, anders werkt u alle rijen bij die u zojuist hebt ingevoegd, wat verspilling zou zijn.