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

Verletzung der UNIQUE KEY-Einschränkung bei INSERT WHERE COUNT(*) =0 auf SQL Server 2005

Warum funktioniert das nicht?

Ich glaube, das Standardverhalten von SQL Server besteht darin, gemeinsam genutzte Sperren freizugeben, sobald sie nicht mehr benötigt werden. Ihre Unterabfrage führt zu einer kurzlebigen gemeinsamen Sperre (S) für die Tabelle, die freigegeben wird, sobald die Unterabfrage abgeschlossen ist.

An diesem Punkt gibt es nichts, was eine gleichzeitige Transaktion daran hindern könnte, genau die Zeile einzufügen, von der Sie gerade bestätigt haben, dass sie nicht vorhanden war.

Welche Änderung muss ich vornehmen, damit es keine Möglichkeit einer Ausnahme aufgrund der Einschränkungsverletzung gibt?

HOLDLOCK hinzufügen Hinweis auf Ihre Unterabfrage weist SQL Server an, die Sperre beizubehalten, bis die Transaktion abgeschlossen ist. (In Ihrem Fall ist dies eine implizite Transaktion.) Der HOLDLOCK hint entspricht dem SERIALIZABLE Hinweis, der selbst der Isolationsstufe für serialisierbare Transaktionen entspricht, auf die Sie sich in Ihrer Liste "anderer Ansätze" beziehen.

Der HOLDLOCK Der Hinweis allein würde ausreichen, um die S-Sperre beizubehalten und zu verhindern, dass eine gleichzeitige Transaktion die Zeile einfügt, vor der Sie sich schützen. Allerdings werden Sie wahrscheinlich feststellen, dass Ihr eindeutiger Schlüsselverletzungsfehler durch Deadlocks ersetzt wird, die mit der gleichen Häufigkeit auftreten.

Wenn Sie nur eine S-Sperre auf der Tabelle beibehalten, ziehen Sie ein Wettrennen zwischen zwei gleichzeitigen Versuchen in Betracht, dieselbe Zeile einzufügen, und gehen Sie dabei im Gleichschritt vor - beiden gelingt es, eine S-Sperre auf der Tabelle zu erwerben, aber keinem von beiden gelingt es, die Exklusive zu erwerben (X) Sperre erforderlich, um die Einfügung auszuführen.

Glücklicherweise gibt es genau für dieses Szenario einen anderen Sperrtyp, der als Update (U)-Sperre bezeichnet wird. Die U-Sperre ist identisch mit einer S-Sperre mit dem folgenden Unterschied:Während mehrere S-Sperren gleichzeitig auf derselben Ressource gehalten werden können, kann jeweils nur eine U-Sperre gehalten werden. (Anders gesagt, während S-Sperren miteinander kompatibel sind (d. h. ohne Konflikt koexistieren können), sind U-Sperren nicht miteinander kompatibel, können aber neben S-Sperren koexistieren; und weiter entlang des Spektrums sind exklusive (X) Sperren nicht kompatibel kompatibel mit S- oder U-Schlössern)

Sie können die implizite S-Sperre Ihrer Unterabfrage mit UPDLOCK zu einer U-Sperre aufrüsten Hinweis.

Zwei gleichzeitige Versuche, dieselbe Zeile in die Tabelle einzufügen, werden nun bei der ersten select-Anweisung serialisiert, da diese eine U-Sperre erwirbt (und hält), die nicht mit einer anderen U-Sperre aus dem gleichzeitigen Einfügeversuch kompatibel ist.

NULL-Werte

Ein separates Problem kann aus der Tatsache entstehen, dass FieldC NULL-Werte zulässt.

Wenn ANSI_NULLS eingeschaltet ist (Standard), dann wird die Gleichheitsprüfung FieldC=NULL durchgeführt würde false zurückgeben, selbst wenn FieldC NULL ist (Sie müssen den IS NULL verwenden Operator zum Prüfen auf Null, wenn ANSI_NULLS ist eingeschaltet). Da FieldC nullable ist, funktioniert Ihre Duplikatprüfung nicht, wenn Sie einen NULL-Wert einfügen.

Um mit Nullen korrekt umzugehen, müssen Sie Ihre EXISTS-Unterabfrage ändern, um IS NULL zu verwenden Operator anstelle von = wenn ein Wert von NULL eingefügt wird. (Oder Sie können die Tabelle ändern, um NULLs in allen betroffenen Spalten zu verbieten.)

SQL Server-Online-Referenzen

  • Hinweise zum Sperren
  • Schlosskompatibilitätsmatrix
  • ANSI_NULLS