it-swarm.com.de

Wann sollte SELECT ... FOR UPDATE verwendet werden?

Bitte helfen Sie mir, den Anwendungsfall hinter SELECT ... FOR UPDATE Zu verstehen.

Frage 1: Ist das Folgende ein gutes Beispiel dafür, wann SELECT ... FOR UPDATE Verwendet werden sollte?

Gegeben:

  • zimmer [id]
  • tags [ID, Name]
  • room_tags [room_id, tag_id]
    • room_id und tag_id sind Fremdschlüssel

Die Anwendung möchte alle Räume und ihre Tags auflisten, muss jedoch zwischen Räumen ohne Tags und Räumen, die entfernt wurden, unterscheiden. Wenn SELECT ... FOR UPDATE nicht verwendet wird, kann Folgendes passieren:

  • Anfänglich:
    • zimmer enthält [id = 1]
    • tags enthält [id = 1, name = 'cats']
    • room_tags enthält [room_id = 1, tag_id = 1]
  • Thread 1: SELECT id FROM rooms;
    • returns [id = 1]
  • Thread 2: DELETE FROM room_tags WHERE room_id = 1;
  • Thread 2: DELETE FROM rooms WHERE id = 1;
  • Thread 2: [schreibt die Transaktion fest]
  • Thread 1: SELECT tags.name FROM room_tags, tags WHERE room_tags.tag_id = 1 AND tags.id = room_tags.tag_id;
    • gibt eine leere Liste zurück

Jetzt denkt Thread 1, dass Raum 1 keine Tags hat, aber in Wirklichkeit wurde der Raum entfernt. Um dieses Problem zu lösen, sollte Thread 1 SELECT id FROM rooms FOR UPDATE Sein, wodurch verhindert wird, dass Thread 2 aus rooms gelöscht wird, bis Thread 1 fertig ist. Ist das korrekt?

Frage 2: Wann sollte man die Transaktionsisolation SERIALIZABLE gegenüber READ_COMMITTED Mit SELECT ... FOR UPDATE Verwenden?

Es wird erwartet, dass Antworten portierbar sind (nicht datenbankspezifisch). Wenn dies nicht möglich ist, erklären Sie bitte warum.

101
Gili

Die einzige tragbare Möglichkeit, die Konsistenz zwischen Räumen und Tags zu gewährleisten und sicherzustellen, dass Räume nach dem Löschen nicht mehr zurückgegeben werden, ist das Sperren mit SELECT FOR UPDATE.

In einigen Systemen ist das Sperren jedoch ein Nebeneffekt der Parallelitätskontrolle, und Sie erzielen dieselben Ergebnisse, ohne FOR UPDATE Explizit anzugeben.


Um dieses Problem zu lösen, sollte Thread 1 SELECT id FROM rooms FOR UPDATE Sein, wodurch verhindert wird, dass Thread 2 aus rooms gelöscht wird, bis Thread 1 fertig ist. Ist das korrekt?

Dies hängt von der Parallelitätskontrolle ab, die Ihr Datenbanksystem verwendet.

  • MyISAM in MySQL (und einigen anderen alten Systemen) sperrt die gesamte Tabelle für die Dauer einer Abfrage.

  • In SQL Server Platzieren SELECT Abfragen gemeinsam genutzte Sperren für die Datensätze/Seiten/Tabellen, die sie untersucht haben, während DML Abfragen Aktualisierungssperren platzieren (die später zu exklusiven oder herabgestuften Sperren heraufgestuft werden gemeinsame Schlösser). Exklusive Sperren sind nicht mit gemeinsam genutzten Sperren kompatibel. Daher wird entweder SELECT oder DELETE Abfrage gesperrt, bis eine andere Sitzung festgeschrieben wird.

  • In Datenbanken, die MVCC verwenden (wie Oracle, PostgreSQL, MySQL mit InnoDB), wird eine DML-Abfrage erstellt Eine Kopie der Aufzeichnung (auf die eine oder andere Weise) und in der Regel Leser blockieren nicht Schriftsteller und umgekehrt. Für diese Datenbanken wäre ein SELECT FOR UPDATE Praktisch: Es würde entweder SELECT oder die DELETE Abfrage sperren, bis eine andere Sitzung festgeschrieben ist, genau wie SQL Server.

Wann sollte man die Transaktionsisolation REPEATABLE_READ Gegenüber READ_COMMITTED Mit SELECT ... FOR UPDATE Verwenden?

Im Allgemeinen verbietet REPEATABLE READ Keine Phantomzeilen (Zeilen, die in einer anderen Transaktion aufgetreten oder verschwunden sind, anstatt geändert zu werden)

  • In Oracle und früheren PostgreSQL Versionen ist REPEATABLE READ Eigentlich ein Synonym für SERIALIZABLE. Grundsätzlich bedeutet dies, dass die Transaktion nach dem Start keine Änderungen mehr sieht. In diesem Setup gibt die letzte Thread 1 - Abfrage den Raum zurück, als wäre er nie gelöscht worden (was möglicherweise nicht das ist, was Sie wollten). Wenn Sie die Räume nach dem Löschen nicht mehr anzeigen möchten, sollten Sie die Zeilen mit SELECT FOR UPDATE Sperren.

  • In InnoDB sind REPEATABLE READ Und SERIALIZABLE verschiedene Dinge: Leser setzen im SERIALIZABLE -Modus die nächsten Tastensperren für die Datensätze, die sie auswerten, und verhindern so effektiv das gleichzeitige DML auf ihnen. Sie brauchen also im serialisierbaren Modus keinen SELECT FOR UPDATE, Sondern nur einen REPEATABLE READ Oder READ COMMITED.

Beachten Sie, dass der Standard für Isolationsmodi vorschreibt, dass Sie bestimmte Macken in Ihren Abfragen nicht sehen, aber nicht definieren, wie (mit Sperren oder mit MVCC oder auf andere Weise).

Wenn ich sage "Sie brauchen nicht SELECT FOR UPDATE", Hätte ich wirklich "wegen der Nebenwirkungen bestimmter Datenbank-Engine-Implementierungen" hinzufügen sollen.

70
Quassnoi

Kurze Antworten:

Q1: ja.

Q2: Egal welche Sie verwenden.

Lange Antwort:

Ein select ... for update Wählt (wie es impliziert) bestimmte Zeilen aus, sperrt sie jedoch auch, als ob sie bereits von der aktuellen Transaktion aktualisiert wurden (oder als ob die Identitätsaktualisierung durchgeführt wurde). Auf diese Weise können Sie sie in der aktuellen Transaktion erneut aktualisieren und anschließend festschreiben ohne dass eine andere Transaktion diese Zeilen in irgendeiner Weise ändern kann.

Eine andere Sichtweise ist, als ob die folgenden zwei Anweisungen atomar ausgeführt würden:

select * from my_table where my_condition;

update my_table set my_column = my_column where my_condition;

Da die von my_condition Betroffenen Zeilen gesperrt sind, kann keine andere Transaktion sie in irgendeiner Weise ändern, und daher spielt die Transaktionsisolationsstufe hier keine Rolle.

Beachten Sie auch, dass die Transaktionsisolationsstufe unabhängig vom Sperren ist: Wenn Sie eine andere Isolationsstufe festlegen, können Sie das Sperren und Aktualisieren von Zeilen in einer anderen Transaktion, die von Ihrer Transaktion gesperrt wurden, nicht umgehen.

Welche Transaktionsisolationsstufen (auf verschiedenen Ebenen) garantieren, ist die Konsistenz der Daten, während Transaktionen ausgeführt werden.

25
Colin 't Hart