Das Muster ist (ohne Fehlerbehandlung):
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;
Sie müssen hier kein zusätzliches Lesen der Tabelle #temp durchführen. Das tun Sie bereits, indem Sie das Update ausprobieren. Zum Schutz vor Racebedingungen gehen Sie genauso vor, als würden Sie einen Block aus zwei oder mehr Anweisungen schützen, die Sie isolieren möchten:Sie packen ihn in eine Transaktion mit einer geeigneten Isolationsstufe (hier wahrscheinlich serialisierbar, obwohl das alles nur macht Sinn, wenn wir nicht über eine #temp-Tabelle sprechen, da diese per Definition serialisiert ist).
Mit einem IF EXISTS
kommen Sie nicht weiter überprüfen (und Sie müssten Sperrhinweise hinzufügen, um dies sowieso sicher / serialisierbar zu machen), aber Sie könnten weiter zurückbleiben, je nachdem, wie oft Sie vorhandene Zeilen aktualisieren und neue einfügen. Das könnte zu einer Menge zusätzlicher E/A führen.
Die Leute werden Ihnen wahrscheinlich sagen, dass Sie MERGE
verwenden sollen (was eigentlich mehrere Operationen hinter den Kulissen sind und auch mit serialisierbar geschützt werden müssen), ich fordere Sie auf, dies nicht zu tun. Ich lege hier dar, warum:
- Seien Sie vorsichtig mit der MERGE-Anweisung von SQL Server
Bei einem mehrzeiligen Muster (wie einem TVP) würde ich das ganz genauso handhaben, aber es gibt keine praktische Möglichkeit, das zweite Lesen zu vermeiden, wie Sie es bei einem einzeiligen Fall können. Und nein, MERGE
vermeidet es auch nicht.
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;
Nun, ich denke, es gibt eine Möglichkeit, dies zu tun, aber ich habe dies nicht gründlich getestet:
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 beiden Fällen führen Sie die Aktualisierung zuerst durch, andernfalls aktualisieren Sie alle Zeilen, die Sie gerade eingefügt haben, was Verschwendung wäre.