Wie sperre ich eine InnoDB-Zeile, die noch nicht existiert?

8

Wie kann ich garantieren, dass ich suchen kann, wenn ein Benutzername in meiner Datenbank existiert, und diesen Benutzernamen dann als neue Zeile in die Datenbank einfügen, ohne dass zwischen den Anweisungen SELECT und INSERT abgefangen wird?

Fast so, als würde ich eine Zeile sperren, die nicht existiert. Ich möchte die nicht existierende Zeile mit dem Benutzernamen "Foo" sperren, so dass ich jetzt prüfen kann, ob sie in der Datenbank existiert UND sie in die Datenbank einfügt, wenn sie nicht schon existiert jede Unterbrechung.

Ich weiß, dass die Verwendung von LOCK IN SHARE MODE und FOR UPDATE existiert, aber soweit ich weiß, funktioniert das nur für Zeilen, die bereits existieren. Ich bin mir nicht sicher, was ich in dieser Situation tun soll.

    
xLite 12.06.2013, 14:55
quelle

3 Antworten

9

Wenn es einen Index für username gibt (was der Fall sein sollte, falls nicht, füge einen hinzu, und ein UNIQUE eins), dann wird die Ausgabe von SELECT * FROM user_table WHERE username = 'foo' FOR UPDATE; verhindern, dass eine gleichzeitige Übertragung diesen Benutzer erstellt ( sowie der "vorherige" und der "nächste" mögliche Wert im Falle eines nicht eindeutigen Indexes.

Wenn kein passender Index gefunden wird (um die Bedingung WHERE zu erfüllen), ist eine effiziente Datensatzsperrung unmöglich und die gesamte Tabelle wird gesperrt *.

Diese Sperre wird bis zum Ende der Transaktion beibehalten, die SELECT ... FOR UPDATE ausgegeben hat.

Einige sehr interessante Informationen zu diesem Thema finden Sie in diesem Handbuch Seiten .

* Ich sage effizient, weil Eine Datensatzsperre ist eigentlich eine Sperre für Indexsätze . Wenn kein passender Index gefunden wird, wird nur der Standard clustered index kann verwendet werden und wird vollständig gesperrt.

    
RandomSeed 12.06.2013, 15:23
quelle
8

Während die obige Antwort wahr ist, da ein SELECT ... FOR UPDATE verhindert, dass gleichzeitige Sitzungen / Transaktionen den gleichen Datensatz einfügen, ist das nicht die volle Wahrheit. Ich kämpfe gerade mit dem gleichen Problem und bin zu dem Schluss gekommen, dass das SELECT ... FOR UPDATE in dieser Situation aus folgendem Grund fast nutzlos ist:

Eine gleichzeitige Transaktion / Sitzung kann auch SELECT ... FOR UPDATE für den gleichen Record / Index-Wert ausführen, und MySQL wird das gerne sofort akzeptieren (nicht blockierend) und keine Fehler werfen. Natürlich, sobald die andere Sitzung das erledigt hat, kann Ihre Sitzung den Datensatz nicht mehr einfügen. Auch Ihre oder die andere Sitzung / Transaktion erhalten keine Informationen über die Situation und denken, dass sie den Datensatz sicher einfügen können, bis sie tatsächlich versuchen, dies zu tun. Wenn versucht wird, einzufügen, führt dies je nach Umständen entweder zu einem Deadlock oder zu einem doppelten Schlüsselfehler.

Mit anderen Worten verhindert SELECT ... FOR UPDATE, dass andere Sitzungen die entsprechenden Datensätze einfügen, ABER selbst wenn Sie SELECT ... FOR UPDATE ausführen und der entsprechende Datensatz nicht gefunden wird, besteht die Möglichkeit, dass Sie das können füge diesen Datensatz tatsächlich ein. IMHO, das macht die "erste Abfrage, dann einfügen" -Methode nutzlos.

Die Ursache des Problems liegt darin, dass MySQL keine Methode anbietet, wirklich nicht existierende Datensätze zu sperren. Zwei gleichzeitige Sitzungen / Transaktionen können gleichzeitig nicht existierende Datensätze "FOR UPDATE" sperren, was eigentlich nicht möglich sein sollte und die Entwicklung erheblich erschwert.

Die einzige Möglichkeit, dies zu umgehen, scheint die Verwendung von Semaphor-Tabellen oder das Sperren der gesamten Tabelle beim Einfügen zu sein. Weitere Informationen zum Sperren ganzer Tabellen oder zum Verwenden von Semaphor-Tabellen finden Sie in der MySQL-Dokumentation.

Nur meine 2 Cent ...

    
Binarus 02.07.2015 12:09
quelle
4

Das Sperren nicht vorhandener Datensätze funktioniert in MySQL nicht. Es gibt mehrere Fehlerberichte darüber:

Eine Problemumgehung besteht darin, eine Mutextabelle zu verwenden Ein vorhandener Datensatz wird gesperrt, bevor der neue Datensatz eingefügt wird. Zum Beispiel gibt es zwei Tabellen: Verkäufer und Produkte. Ein Verkäufer hat viele Produkte, sollte aber keine doppelten Produkte haben. In diesem Fall kann die sellers-Tabelle als Mutextabelle verwendet werden. Bevor ein neues Produkt eingefügt wird, wird eine Sperre auf dem Verkäufer-Datensatz erstellt. Mit dieser zusätzlichen Abfrage ist sichergestellt, dass jeweils nur ein Thread die Aktion ausführen kann. Kein Duplikat Kein Deadlock.

    
Yanhao 12.06.2016 03:56
quelle