Wir begegnen oft der Situation "Wenn nicht vorhanden, einfügen". Dan Guzmans Blog bietet eine exzellente Untersuchung, wie man diesen Prozess threadsicher macht.
Ich habe eine Basistabelle, die einfach eine Zeichenfolge zu einer Ganzzahl von a katalogisiert SEQUENCE
. In einer gespeicherten Prozedur muss ich entweder den Integer-Schlüssel für den Wert abrufen, falls vorhanden, oder INSERT
ihn und dann den resultierenden Wert. Die dbo.NameLookup.ItemName
Spalte unterliegt einer Eindeutigkeitsbeschränkung, sodass die Datenintegrität nicht gefährdet ist, aber ich möchte nicht auf die Ausnahmen stoßen.
Es ist kein, IDENTITY
also kann ich nicht bekommen SCOPE_IDENTITY
und der Wert könnte NULL
in bestimmten Fällen sein.
In meiner Situation muss ich mich nur mit der INSERT
Sicherheit auf dem Tisch befassen, also versuche ich zu entscheiden, ob es besser ist, MERGE
wie folgt vorzugehen:
SET NOCOUNT, XACT_ABORT ON;
DECLARE @vValueId INT
DECLARE @inserted AS TABLE (Id INT NOT NULL)
MERGE
dbo.NameLookup WITH (HOLDLOCK) AS f
USING
(SELECT @vName AS val WHERE @vName IS NOT NULL AND LEN(@vName) > 0) AS new_item
ON f.ItemName= new_item.val
WHEN MATCHED THEN
UPDATE SET @vValueId = f.Id
WHEN NOT MATCHED BY TARGET THEN
INSERT
(ItemName)
VALUES
(@vName)
OUTPUT inserted.Id AS Id INTO @inserted;
SELECT @vValueId = s.Id FROM @inserted AS s
Ich könnte dies ohne Verwendung MERGE
von nur einer Bedingung tun , INSERT
gefolgt von einer SELECT
Ich denke, dass dieser zweite Ansatz für den Leser klarer ist, aber ich bin nicht überzeugt, dass es eine "bessere" Praxis ist
SET NOCOUNT, XACT_ABORT ON;
INSERT INTO
dbo.NameLookup (ItemName)
SELECT
@vName
WHERE
NOT EXISTS (SELECT * FROM dbo.NameLookup AS t WHERE @vName IS NOT NULL AND LEN(@vName) > 0 AND t.ItemName = @vName)
DECLARE @vValueId int;
SELECT @vValueId = i.Id FROM dbo.NameLookup AS i WHERE i.ItemName = @vName
Oder vielleicht gibt es einen anderen besseren Weg, den ich nicht in Betracht gezogen habe
Ich habe andere Fragen gesucht und referenziert. Diese: /programming/5288283/sql-server-insert-if-not-exists-best-practice ist die am besten geeignete, die ich finden konnte, scheint aber für meinen Anwendungsfall nicht sehr zutreffend zu sein. Andere Fragen zu dem IF NOT EXISTS() THEN
Ansatz, die ich nicht für akzeptabel halte.
quelle
Antworten:
Da Sie eine Sequenz verwenden, können Sie dieselbe NEXT VALUE FOR- Funktion verwenden, die Sie bereits in einer Standardeinschränkung für das
Id
Primärschlüsselfeld verwendet haben, um im Voraus einen neuenId
Wert zu generieren . Das erste Generieren des Werts bedeutet, dass Sie sich keine Sorgen machen müssenSCOPE_IDENTITY
, was bedeutet, dass Sie weder dieOUTPUT
Klausel noch ein zusätzlichesSELECT
Element benötigen , um den neuen Wert zu erhalten. Sie werden den Wert haben, bevor Sie das tunINSERT
, und Sie müssen sich nicht einmal damit anlegenSET IDENTITY INSERT ON / OFF
:-)Das kümmert sich also um einen Teil der Gesamtsituation. Der andere Teil behandelt das Problem der gleichzeitigen Ausführung von zwei Prozessen genau zur gleichen Zeit, findet keine vorhandene Zeile für genau die gleiche Zeichenfolge und fährt mit fort
INSERT
. Es geht darum, die Verletzung der eindeutigen Einschränkung zu vermeiden, die auftreten würde.Eine Möglichkeit, diese Art von Parallelitätsproblemen zu behandeln, besteht darin, diesen bestimmten Vorgang als Single-Thread-Vorgang zu erzwingen. Der Weg, dies zu tun, ist die Verwendung von Anwendungssperren (die über Sitzungen hinweg funktionieren). Während sie effektiv sind, können sie für eine Situation wie diese, in der die Häufigkeit von Kollisionen wahrscheinlich ziemlich gering ist, etwas unbeholfen sein.
Die andere Möglichkeit, mit den Kollisionen umzugehen, besteht darin, zu akzeptieren, dass sie manchmal auftreten, und mit ihnen umzugehen, anstatt zu versuchen, sie zu vermeiden. Mit dem
TRY...CATCH
Konstrukt können Sie einen bestimmten Fehler (in diesem Fall: "Unique Constraint Violation", Nachricht 2601) effektiv abfangen und erneut ausführenSELECT
, um denId
Wert abzurufen, da wir wissen, dass er jetzt existiert, weil er sichCATCH
mit diesem bestimmten im Block befindet Error. Andere Fehler können im typischen behandelt werdenRAISERROR
/RETURN
oderTHROW
Art und Weise.Testkonfiguration: Sequenz, Tabelle und eindeutiger Index
Testkonfiguration: Gespeicherte Prozedur
Der Test
Frage von OP
MERGE
hat verschiedene "Probleme" (mehrere Referenzen sind in der Antwort von @ SqlZim verlinkt, so dass Sie diese Informationen hier nicht duplizieren müssen). Außerdem gibt es bei diesem Ansatz keine zusätzliche Sperre (weniger Konflikte), sodass die Parallelität verbessert werden sollte. Bei diesem Ansatz wird es nie zu einer Unique Constraint-Verletzung kommen, ganz ohneHOLDLOCK
, usw. Es ist so gut wie garantiert, dass es funktioniert.Die Gründe für diesen Ansatz sind:
CATCH
Block fällt , ziemlich gering. Es ist sinnvoller, den Code zu optimieren, der 99% der Zeit ausgeführt wird, als den Code, der 1% der Zeit ausgeführt wird (es sei denn, es sind keine Kosten für die Optimierung von beiden erforderlich, dies ist hier jedoch nicht der Fall).Kommentar von @ SqlZims Antwort (Hervorhebung hinzugefügt)
Ich würde diesem ersten Satz zustimmen, wenn er geändert würde, um "und wenn besonnen" zu sagen. Nur weil etwas technisch möglich ist, heißt das nicht, dass die Situation (dh der beabsichtigte Anwendungsfall) davon profitiert.
Das Problem, das ich bei diesem Ansatz sehe, ist, dass es mehr sperrt als vorgeschlagen wird. Es ist wichtig, die zitierte Dokumentation zu "serializable" erneut zu lesen, insbesondere die folgenden (Hervorhebung hinzugefügt):
Hier ist der Kommentar im Beispielcode:
Das operative Wort dort ist "Reichweite". Die Sperre wird nicht nur auf den Wert in
@vName
, sondern genauer gesagt auf einen Bereich, der bei beginntDie Position, an der dieser neue Wert abgelegt werden soll (dh zwischen den vorhandenen Schlüsselwerten auf beiden Seiten, auf die der neue Wert passt), nicht jedoch der Wert selbst. Das heißt, andere Prozesse können keine neuen Werte einfügen, je nachdem, welche Werte gerade gesucht werden. Wenn die Suche am oberen Ende des Bereichs ausgeführt wird, wird das Einfügen von Elementen, die dieselbe Position einnehmen könnten, blockiert. Wenn beispielsweise die Werte "a", "b" und "d" vorhanden sind und ein Prozess "f" auswählt, ist es nicht möglich, die Werte "g" oder sogar "e" einzufügen ( da einer von denen wird sofort nach "d" kommen). Das Einfügen eines Werts von "c" ist jedoch möglich, da dieser Wert nicht in den Bereich "reserviert" gestellt wird.Das folgende Beispiel soll dieses Verhalten veranschaulichen:
(In der Abfrage-Registerkarte (dh Sitzung) # 1)
(In der Abfrage-Registerkarte (dh Sitzung) # 2)
Wenn der Wert "C" vorhanden ist und der Wert "A" ausgewählt (und daher gesperrt) ist, können Sie den Wert "D", jedoch nicht den Wert "B" eingeben:
(In der Abfrage-Registerkarte (dh Sitzung) # 1)
(In der Abfrage-Registerkarte (dh Sitzung) # 2)
Um ehrlich zu sein, gibt es in meinem vorgeschlagenen Ansatz, wenn es eine Ausnahme gibt, 4 Einträge im Transaktionsprotokoll, die bei diesem "serialisierbaren Transaktionsansatz" nicht vorkommen. ABER, wie ich oben sagte, wenn die Ausnahme 1% (oder sogar 5%) der Zeit auftritt, ist dies weitaus weniger belastend als der weitaus wahrscheinlichere Fall, dass das anfängliche SELECT INSERT-Operationen vorübergehend blockiert.
Ein weiteres, wenn auch geringfügiges Problem bei diesem Ansatz mit "serialisierbarer Transaktion + OUTPUT-Klausel" ist, dass die
OUTPUT
Klausel (in ihrer derzeitigen Verwendung) die Daten als Ergebnismenge zurücksendet. Eine Ergebnismenge erfordert mehr Aufwand (wahrscheinlich auf beiden Seiten: in SQL Server zum Verwalten des internen Cursors und in der App-Ebene zum Verwalten des DataReader-Objekts) als ein einfacherOUTPUT
Parameter. Da es sich nur um einen einzelnen Skalarwert handelt und davon ausgegangen wird, dass es sich um eine hohe Ausführungshäufigkeit handelt, summiert sich der zusätzliche Aufwand für die Ergebnismenge wahrscheinlich auf.Während die
OUTPUT
Klausel so verwendet werden könnteOUTPUT
, dass ein Parameter zurückgegeben wird, wären zusätzliche Schritte erforderlich, um eine temporäre Tabelle oder Tabellenvariable zu erstellen und anschließend den Wert aus dieser temporären Tabelle / Tabellenvariablen in denOUTPUT
Parameter auszuwählen .Weitere Klarstellung: Antwort auf @ SqlZims Antwort (aktualisierte Antwort) auf meine Antwort auf @ SqlZims Antwort (in der ursprünglichen Antwort) auf meine Aussage zu Nebenläufigkeit und Leistung ;-)
Tut mir leid, wenn dieser Teil ein bisschen lang ist, aber an diesem Punkt sind wir nur auf die Nuancen der beiden Ansätze beschränkt.
Ja, ich gebe zu, dass ich voreingenommen bin, wenn auch um fair zu sein:
INSERT
Versuch aufgrund einer Unique Constraint-Verletzung fehlschlägt. Das habe ich in keiner der anderen Antworten / Posts gesehen.In Bezug auf @ gbns "JFDI" -Ansatz, Michael J. Swarts "Ugly Pragmatism For The Win" -Post und Aaron Bertrands Kommentar zu Michaels Post (in Bezug auf seine Tests, die zeigen, welche Szenarien die Leistung verringert haben) sowie Ihren Kommentar zu Ihrer "Anpassung von Michael J Stewarts Adaption der JFDI-Prozedur "Try Catch" von @ gbn mit folgenden Worten:
In Bezug auf diese Diskussion zwischen gbn / Michael / Aaron in Bezug auf den "JFDI" -Ansatz wäre es falsch, meinen Vorschlag mit dem "JFDI" -Ansatz von gbn gleichzusetzen. Aufgrund der Art des Vorgangs "Abrufen oder Einfügen" ist es ausdrücklich erforderlich
SELECT
, denID
Wert für vorhandene Datensätze abzurufen. Dieses SELECT dient alsIF EXISTS
Überprüfung, wodurch dieser Ansatz eher der "CheckTryCatch" -Variation von Aarons Tests entspricht. Michaels umgeschriebener Code (und Ihre letzte Adaption von Michaels Adaption) beinhaltet auchWHERE NOT EXISTS
, dass Sie diese Prüfung zuerst durchführen müssen. Daher trifft mein Vorschlag (zusammen mit Michaels endgültigem Code und Ihrer Anpassung seines endgültigen Codes) denCATCH
Block nicht allzu oft. Es könnten nur Situationen sein, in denen zwei Sitzungen,ItemName
INSERT...SELECT
genau im selben Moment, so dass beide Sitzungen ein "true" fürWHERE NOT EXISTS
genau im selben Moment erhalten und somit beide versuchen,INSERT
das genau im selben Moment zu tun . Dieses sehr spezielle Szenario ist weitaus seltener als das Auswählen eines vorhandenen SzenariosItemName
oder das Einfügen eines neuen Szenarios,ItemName
wenn kein anderer Prozess dies im selben Moment versucht .IM HINBLICK AUF DAS OBENE: Warum bevorzuge ich meinen Ansatz?
Schauen wir uns zunächst an, welche Sperren beim "serialisierbaren" Ansatz stattfinden. Wie oben erwähnt, hängt der "Bereich", der gesperrt wird, von den vorhandenen Schlüsselwerten zu beiden Seiten ab, zu denen der neue Schlüsselwert passen würde. Der Anfang oder das Ende des Bereichs kann auch der Anfang oder das Ende des Index sein, wenn in dieser Richtung kein Schlüsselwert vorhanden ist. Angenommen, wir haben den folgenden Index und die folgenden Schlüssel (
^
stellt den Anfang des Index dar, während$
er das Ende darstellt):Wenn Sitzung 55 versucht, einen Schlüsselwert einzufügen von:
A
, dann ist der Bereich # 1 (von^
bisC
) gesperrt: Sitzung 56 kann keinen Wert von einfügenB
, selbst wenn er eindeutig und gültig ist (noch). Aber Sitzung 56 kann Werte einfügenD
,G
undM
.D
, dann ist der Bereich 2 (vonC
bisF
) gesperrt: Sitzung 56 kannE
(noch) keinen Wert von einfügen . Aber Sitzung 56 kann Werte einfügenA
,G
undM
.M
, dann ist der Bereich 4 (vonJ
bis$
) gesperrt: Sitzung 56 kannX
(noch) keinen Wert von einfügen . Aber Sitzung 56 kann Werte einfügenA
,D
undG
.Wenn mehr Schlüsselwerte hinzugefügt werden, werden die Bereiche zwischen den Schlüsselwerten enger, wodurch die Wahrscheinlichkeit / Häufigkeit verringert wird, dass mehrere Werte gleichzeitig in den gleichen Bereich eingefügt werden. Zugegebenermaßen ist dies kein großes Problem, und glücklicherweise scheint es sich um ein Problem zu handeln, das im Laufe der Zeit tatsächlich abnimmt.
Das Problem mit meinem Ansatz wurde oben beschrieben: Es tritt nur auf, wenn zwei Sitzungen gleichzeitig versuchen, denselben Schlüsselwert einzufügen . In dieser Hinsicht kommt es darauf an, wie hoch die Wahrscheinlichkeit ist, dass etwas passiert: Es werden zwei verschiedene, aber nahe beieinander liegende Schlüsselwerte gleichzeitig versucht, oder es wird derselbe Schlüsselwert gleichzeitig versucht? Ich nehme an, die Antwort liegt in der Struktur der App, die die Einfügungen ausführt, aber im Allgemeinen würde ich annehmen, dass es wahrscheinlicher ist, dass zwei verschiedene Werte eingefügt werden, die zufällig denselben Bereich teilen. Die einzige Möglichkeit, dies wirklich zu wissen, besteht darin, beide auf dem OP-System zu testen.
Als nächstes betrachten wir zwei Szenarien und wie jeder Ansatz damit umgeht:
Alle Anfragen beziehen sich auf eindeutige Schlüsselwerte:
In diesem Fall wird der
CATCH
Block in meinem Vorschlag nie eingegeben, daher kein "Problem" (dh 4 Transprotokolleinträge und die dafür erforderliche Zeit). Bei der "serialisierbaren" Methode besteht jedoch immer die Möglichkeit, andere Einfügungen im gleichen Bereich zu blockieren (wenn auch nicht für sehr lange Zeit), auch wenn alle Einfügungen eindeutig sind.Hohe Häufigkeit von Anfragen für denselben Schlüsselwert zur selben Zeit:
In diesem Fall - eine sehr geringe Eindeutigkeit in Bezug auf eingehende Anfragen nach nicht vorhandenen Schlüsselwerten - wird der
CATCH
Block in meinem Vorschlag regelmäßig eingetragen. Dies hat zur Folge, dass für jede fehlgeschlagene Einfügung ein automatischer Rollback durchgeführt und die 4 Einträge in das Transaktionsprotokoll geschrieben werden müssen, was jedes Mal einen leichten Leistungseinbruch darstellt. Die Gesamtoperation sollte jedoch niemals fehlschlagen (zumindest nicht deshalb).(Es gab ein Problem mit der vorherigen Version des "aktualisierten" Ansatzes, bei dem Deadlocks aufgetreten sind. Es
updlock
wurde ein Hinweis hinzugefügt, der dieses Problem behebt und keine Deadlocks mehr verursacht.)ABER in der "serialisierbaren" Methode (sogar in der aktualisierten, optimierten Version) blockiert der Vorgang. Warum? Weil dasserializable
Verhalten nurINSERT
Operationen in dem Bereich verhindert, der gelesen und daher gesperrt wurde;SELECT
Operationen in diesem Bereich werden dadurch nicht verhindert .In
serializable
diesem Fall scheint der Ansatz keinen zusätzlichen Overhead zu haben und könnte eine geringfügig bessere Leistung erbringen als von mir vorgeschlagen.Wie bei vielen / den meisten Diskussionen zur Leistung ist der einzige Weg, um wirklich ein Gefühl dafür zu bekommen, wie sich etwas auswirkt, es in der Zielumgebung auszuprobieren, in der es ausgeführt wird, da es so viele Faktoren gibt, die sich auf das Ergebnis auswirken können. An diesem Punkt wird es keine Ansichtssache sein :).
quelle
Aktualisierte Antwort
Antwort an @srutzky
Ich bin damit einverstanden und aus den gleichen Gründen verwende ich Ausgabeparameter, wenn ich umsichtig bin . Es war mein Fehler, bei meiner ersten Antwort keinen Ausgabeparameter zu verwenden. Ich war faul.
Hier ist eine überarbeitete Prozedur mit einem Ausgabeparameter, zusätzlichen Optimierungen und dem,
next value for
was @srutzky in seiner Antwort erklärt :Hinweis zum Update : Durch das Einbeziehen
updlock
der Option select werden in diesem Szenario die richtigen Sperren aktiviert. Vielen Dank an @srutzky, der darauf hingewiesen hat, dass dies zu Deadlocks führen kann, wenn es nurserializable
auf dem Computer verwendet wirdselect
.Hinweis: Dies ist möglicherweise nicht der Fall, aber wenn dies möglich ist, wird die Prozedur mit einem Wert für
@vValueId
, includeset @vValueId = null;
after aufgerufenset xact_abort on;
, andernfalls kann sie entfernt werden.Zu @ srutzkys Beispielen für das Sperrverhalten von Schlüsselbereichen:
@srutzky verwendet nur einen Wert in seiner Tabelle und sperrt den Schlüssel "next" / "infinity" für seine Tests, um das Sperren des Schlüsselbereichs zu veranschaulichen. Während seine Tests veranschaulichen, was in diesen Situationen passiert, glaube ich, dass die Art und Weise, in der die Informationen präsentiert werden, zu falschen Annahmen über die Menge an Sperren führen kann, die bei der Verwendung
serializable
in dem in der ursprünglichen Frage dargestellten Szenario zu erwarten sind .Auch wenn ich eine Voreingenommenheit (vielleicht fälschlicherweise) in der Art und Weise wahrnehme, in der er seine Erklärungen und Beispiele für die Tastensperrung präsentiert, sind sie immer noch richtig.
Nach weiteren Recherchen fand ich einen besonders relevanten Blog-Artikel von Michael J. Swart aus dem Jahr 2011: Mythbusting: Concurrent Update / Insert Solutions . Darin testet er mehrere Methoden auf Genauigkeit und Gleichzeitigkeit. Methode 4: Erhöhte Isolation + Feineinstellung von Sperren basiert auf Sam Saffrons Einfüge- oder Aktualisierungsmuster für SQL Server und ist die einzige Methode im ursprünglichen Test, mit der seine Erwartungen erfüllt wurden (zusammen mit
merge with (holdlock)
).Im Februar 2016 veröffentlichte Michael J. Swart Ugly Pragmatism For The Win . In diesem Beitrag behandelt er einige zusätzliche Anpassungen, die er an seinen Safran-Upsert-Prozeduren vorgenommen hat, um die Sperrung zu verringern (die ich in die obige Prozedur aufgenommen habe).
Nachdem er diese Änderungen vorgenommen hatte, war Michael nicht glücklich darüber, dass sein Verfahren immer komplizierter wurde, und befragte einen Kollegen namens Chris. Chris hat den gesamten ursprünglichen Mythbusters-Beitrag gelesen, alle Kommentare gelesen und nach dem TRY CATCH JFDI-Muster von @ gbn gefragt . Dieses Muster ähnelt @ srutzkys Antwort und ist die Lösung, die Michael letztendlich in dieser Instanz verwendet hat.
Michael J Swart:
Meiner Meinung nach sind beide Lösungen machbar. Ich ziehe es zwar immer noch vor, die Isolationsstufe zu erhöhen und die Sperren fein abzustimmen, aber @ srutzkys Antwort ist auch gültig und kann in Ihrer spezifischen Situation leistungsfähiger sein oder auch nicht.
Vielleicht komme ich auch in Zukunft zu dem gleichen Schluss wie Michael J. Swart, aber ich bin einfach noch nicht da.
Es ist nicht meine Präferenz, aber hier ist, wie meine Anpassung von Michael J. Stewarts Adaption von @ gbns Try Catch JFDI- Prozedur aussehen würde:
Wenn Sie öfter neue Werte einfügen als vorhandene Werte auswählen, ist dies möglicherweise performanter als die Version von @ srutzky . Ansonsten würde ich @ srutzkys Version dieser vorziehen .
Aaron Bertrands Kommentare zu Michael J Swarts Beitrag verweisen auf relevante Tests, die er durchgeführt hat, und führten zu diesem Austausch. Auszug aus dem Kommentarbereich zu Ugly Pragmatism For the Win :
und die Antwort von:
Neue Links:
Ursprüngliche Antwort
Ich bevorzuge immer noch den Upsert-Ansatz von Sam Saffron gegenüber der Verwendung
merge
, insbesondere wenn es sich um eine einzelne Zeile handelt.Ich würde diese Upsert-Methode folgendermaßen an diese Situation anpassen:
Ich würde mit Ihrer Benennung übereinstimmen, und so wie es
serializable
istholdlock
, wählen Sie eine aus und verwenden Sie sie konsequent. Ich neige dazu, zu verwenden,serializable
weil es derselbe Name ist, der bei der Angabe verwendet wirdset transaction isolation level serializable
.Durch die Verwendung von
serializable
oder wirdholdlock
eine Bereichssperre verwendet, die auf dem Wert basiert, der@vName
alle anderen Operationen warten lässt, wenn sie Werte auswählen oder einfügendbo.NameLookup
, die den Wert in derwhere
Klausel enthalten.Damit die Bereichssperre ordnungsgemäß funktioniert, muss ein Index für die
ItemName
Spalte vorhanden sein. Dies gilt auch für die Verwendungmerge
.Hier ist , was das Verfahren aussehen würde meist folgenden Erland Sommarskog des White Paper für die Fehlerbehandlung , mit
throw
. Wennthrow
Sie Ihre Fehler nicht auslösen, ändern Sie sie so, dass sie mit den übrigen Verfahren übereinstimmen:Um zusammenzufassen, was in der obigen Prozedur vor sich geht:
set nocount on; set xact_abort on;
Wie Sie es immer tun , dann ist unsere Eingabevariableis null
oder leerselect id = cast(null as int)
das Ergebnis. Wenn es nicht null oder leer ist, dann holen Sie dasId
für unsere Variable, während Sie diese Stelle halten, falls es nicht da ist. Wenn der daId
ist, schick ihn raus. Wenn es nicht da ist, legen Sie es ein und senden Sie das neueId
.Währenddessen warten andere Aufrufe dieser Prozedur, die versuchen, die ID für denselben Wert zu finden, bis die erste Transaktion abgeschlossen ist, und wählen Sie diese aus und geben Sie sie zurück. Andere Aufrufe dieser Prozedur oder andere Anweisungen, die nach anderen Werten suchen, werden fortgesetzt, da dies nicht im Wege steht.
Obwohl ich mit @srutzky einverstanden bin, dass Sie mit Kollisionen umgehen und die Ausnahmen für diese Art von Problem schlucken können, ziehe ich es persönlich vor, eine maßgeschneiderte Lösung zu finden, um dies nach Möglichkeit zu vermeiden. In diesem Fall bin ich nicht der Meinung, dass die Verwendung der Sperren von
serializable
ein umständlicher Ansatz ist, und ich wäre zuversichtlich, dass sie mit hoher Parallelität gut umgehen können.Zitat aus der SQL Server-Dokumentation auf der Tabelle Hinweise
serializable
/holdlock
:Zitat aus der SQL Server-Dokumentation zur Transaktionsisolationsstufe
serializable
Links zur obigen Lösung:
Muster für SQL Server einfügen oder aktualisieren - Sam Saffron
Dokumentation zu serialisierbaren und anderen Tabellenhinweisen - MSDN
Fehler- und Transaktionsbehandlung in SQL Server Teil 1 - Jumpstart- Fehlerbehandlung - Erland Sommarskog
Erland Sommarskogs Rat bezüglich @@ rowcount , dem ich in diesem Fall nicht gefolgt bin.
MERGE
hat einen fleckigen Verlauf, und es scheint mehr Zeit in Anspruch zu nehmen, um sicherzustellen, dass sich der Code unter all dieser Syntax so verhält, wie Sie es möchten. Relevantemerge
Artikel:Ein interessanter MERGE Bug - Paul White
UPSERT Race Condition With Merge - Sqlteam
Vorsicht bei der MERGE-Anweisung von SQL Server - Aaron Bertrand
Kann ich diese Merge-Anweisung optimieren - Aaron Bertrand
Wenn Sie indizierte Ansichten und MERGE verwenden, lesen Sie dies bitte! - Aaron Bertrand
Ein letztes Glied, Kendra Little, hat einen groben Vergleich von
merge
vsinsert with left join
mit dem Vorbehalt angestellt, dass "ich keine gründlichen Belastungstests durchgeführt habe", aber es ist immer noch eine gute Lektüre.quelle