Sqlserver
 sql >> Datenbank >  >> RDS >> Sqlserver

Verwenden einer if-Bedingung in einem SQL Server zum Einfügen

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.