Fügen Sie den auf SQL Server gespeicherten Update-Prozess ein

104

Ich habe einen gespeicherten Prozess geschrieben, der ein Update ausführt, wenn ein Datensatz vorhanden ist, andernfalls wird ein Insert ausgeführt. Es sieht ungefähr so ​​aus:

update myTable set Col1=@col1, Col2=@col2 where ID=@ID
if @@rowcount = 0
insert into myTable (Col1, Col2) values (@col1, @col2)

Meine Logik hinter dem Schreiben auf diese Weise ist, dass das Update eine implizite Auswahl unter Verwendung der where-Klausel ausführt. Wenn diese 0 zurückgibt, erfolgt die Einfügung.

Die Alternative dazu wäre, eine Auswahl zu treffen und dann basierend auf der Anzahl der zurückgegebenen Zeilen entweder eine Aktualisierung oder eine Einfügung durchzuführen. Dies habe ich als ineffizient angesehen, da bei einem Update zwei Auswahlen durchgeführt werden (der erste explizite Auswahlaufruf und der zweite implizit im Wo des Updates). Wenn der Proc eine Einfügung machen würde, gäbe es keinen Unterschied in der Effizienz.

Ist meine Logik hier? Würden Sie auf diese Weise eine Einfügung und ein Update in einem gespeicherten Prozess kombinieren?

Kerl
quelle

Antworten:

61

Ihre Annahme ist richtig, dies ist der optimale Weg, und es heißt Upsert / Merge .

Bedeutung von UPSERT - von sqlservercentral.com :

Für jedes Update in dem oben genannten Fall entfernen wir einen zusätzlichen Lesevorgang aus der Tabelle, wenn wir UPSERT anstelle von EXISTS verwenden. Leider verwenden sowohl eine UPSERT- als auch eine IF EXISTS-Methode für ein Insert die gleiche Anzahl von Lesevorgängen in der Tabelle. Daher sollte die Existenzprüfung nur durchgeführt werden, wenn ein sehr triftiger Grund vorliegt, die zusätzliche E / A zu rechtfertigen. Die optimierte Vorgehensweise besteht darin, sicherzustellen, dass Sie so wenig Lesevorgänge wie möglich in der Datenbank haben.

Die beste Strategie ist es, das Update zu versuchen. Wenn vom Update keine Zeilen betroffen sind, fügen Sie ein. In den meisten Fällen ist die Zeile bereits vorhanden und es ist nur eine E / A erforderlich.

Bearbeiten : Bitte lesen Sie diese Antwort und den verlinkten Blog-Beitrag, um mehr über die Probleme mit diesem Muster und darüber zu erfahren, wie es sicher funktioniert.

binOr
quelle
1
Nun, ich denke, es hat mindestens eine Frage beantwortet. Und ich habe keinen Code hinzugefügt, weil der Code in der Frage für mich bereits richtig schien. Obwohl ich es in eine Transaktion einbauen würde, habe ich die Isolationsstufe für das Update nicht berücksichtigt. Vielen Dank, dass Sie in Ihrer Antwort darauf hingewiesen haben!
binOr
54

Bitte lesen Sie den Beitrag in meinem Blog für ein gutes, sicheres Muster, das Sie verwenden können. Es gibt viele Überlegungen, und die akzeptierte Antwort auf diese Frage ist alles andere als sicher.

Versuchen Sie für eine schnelle Antwort das folgende Muster. Es funktioniert gut unter SQL 2000 und höher. SQL 2005 bietet Ihnen eine Fehlerbehandlung, die andere Optionen eröffnet, und SQL 2008 bietet Ihnen einen MERGE-Befehl.

begin tran
   update t with (serializable)
   set hitCount = hitCount + 1
   where pk = @id
   if @@rowcount = 0
   begin
      insert t (pk, hitCount)
      values (@id,1)
   end
commit tran
Sam Safran
quelle
1
In Ihrem Blog-Beitrag schließen Sie mit der Verwendung des WITH-Hinweises (Updlock, serialisierbar) in der Existenzprüfung. Beim Lesen von MSDN heißt es jedoch: "UPDLOCK - Gibt an, dass Aktualisierungssperren genommen und gehalten werden sollen, bis die Transaktion abgeschlossen ist." Bedeutet dies, dass der serialisierbare Hinweis überflüssig ist, da die Aktualisierungssperre für den Rest der Transaktion ohnehin beibehalten wird, oder habe ich etwas falsch verstanden?
Dan Def
10

Wenn mit SQL Server 2000/2005 verwendet werden soll, muss der ursprüngliche Code in die Transaktion eingeschlossen werden, um sicherzustellen, dass die Daten im gleichzeitigen Szenario konsistent bleiben.

BEGIN TRANSACTION Upsert
update myTable set Col1=@col1, Col2=@col2 where ID=@ID
if @@rowcount = 0
insert into myTable (Col1, Col2) values (@col1, @col2)
COMMIT TRANSACTION Upsert

Dies verursacht zusätzliche Leistungskosten, stellt jedoch die Datenintegrität sicher.

Fügen Sie, wie bereits vorgeschlagen, MERGE hinzu, sofern verfügbar.

Dima Malenko
quelle
8

MERGE ist übrigens eine der neuen Funktionen in SQL Server 2008.

Jon Galloway
quelle
und du solltest es unbedingt lieber benutzen als diesen schwer zu lesenden Homebrew-Unsinn. Gutes Beispiel ist hier - mssqltips.com/sqlservertip/1704/…
Rich Bryant
6

Sie müssen es nicht nur in einer Transaktion ausführen, sondern auch eine hohe Isolationsstufe. Ich glaube, die Standardisolationsstufe ist Read Commited und dieser Code muss serialisierbar sein.

SET transaction isolation level SERIALIZABLE
BEGIN TRANSACTION Upsert
UPDATE myTable set Col1=@col1, Col2=@col2 where ID=@ID
if @@rowcount = 0
  begin
    INSERT into myTable (ID, Col1, Col2) values (@ID @col1, @col2)
  end
COMMIT TRANSACTION Upsert

Vielleicht ist es eine gute Idee, auch die @@ Fehlerprüfung und das Rollback hinzuzufügen.

Tomas Tintera
quelle
@ Munish Goyal Weil in der Datenbank mehrere Befehle und Vorgänge parallel ausgeführt werden. Dann kann ein anderer Thread eine Zeile unmittelbar nach dem Ausführen des Updates und vor dem Ausführen des Einfügens einfügen.
Tomas Tintera
5

Wenn Sie in SQL 2008 keine Zusammenführung durchführen, müssen Sie Folgendes ändern:

wenn @@ rowcount = 0 und @@ error = 0

Wenn andernfalls das Update aus irgendeinem Grund fehlschlägt, wird versucht, es anschließend einzufügen, da die Zeilenanzahl für eine fehlgeschlagene Anweisung 0 ist

Simon Munro
quelle
3

Der große Fan von UPSERT reduziert den zu verwaltenden Code erheblich. Hier ist eine andere Möglichkeit, wie ich es mache: Einer der Eingabeparameter ist ID. Wenn die ID NULL oder 0 ist, wissen Sie, dass es sich um ein INSERT handelt, andernfalls handelt es sich um ein Update. Angenommen, die Anwendung weiß, ob eine ID vorhanden ist, funktioniert also nicht in allen Situationen, halbiert jedoch die Ausführung, wenn Sie dies tun.

Natron
quelle
2

Geänderter Beitrag von Dima Malenko:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE 

BEGIN TRANSACTION UPSERT 

UPDATE MYTABLE 
SET    COL1 = @col1, 
       COL2 = @col2 
WHERE  ID = @ID 

IF @@rowcount = 0 
  BEGIN 
      INSERT INTO MYTABLE 
                  (ID, 
                   COL1, 
                   COL2) 
      VALUES      (@ID, 
                   @col1, 
                   @col2) 
  END 

IF @@Error > 0 
  BEGIN 
      INSERT INTO MYERRORTABLE 
                  (ID, 
                   COL1, 
                   COL2) 
      VALUES      (@ID, 
                   @col1, 
                   @col2) 
  END 

COMMIT TRANSACTION UPSERT 

Sie können den Fehler abfangen und den Datensatz an eine fehlgeschlagene Einfügetabelle senden.
Ich musste dies tun, weil wir alle Daten, die über WSDL gesendet werden, nehmen und wenn möglich intern reparieren.

thughes78013
quelle
1

Ihre Logik scheint solide zu sein, aber Sie sollten in Betracht ziehen, Code hinzuzufügen, um das Einfügen zu verhindern, wenn Sie einen bestimmten Primärschlüssel übergeben haben.

Wenn Sie andernfalls immer einfügen, wenn das Update keine Datensätze beeinflusst hat, was passiert dann, wenn jemand den Datensatz löscht, bevor "UPSERT" ausgeführt wird? Jetzt existiert der Datensatz, den Sie aktualisieren wollten, nicht mehr, sodass stattdessen ein Datensatz erstellt wird. Das ist wahrscheinlich nicht das Verhalten, nach dem Sie gesucht haben.

Kevin Fairchild
quelle