2014-07-02 15 views
5

Wenn ich eine Datenbanktransaktion zum Gruppieren mehrerer Aktualisierungen verwende, sollte ich auch SELECTs in die Transaktion aufnehmen? Zum Beispiel können, sage ich:Sollte ich SELECTs in eine Transaktion aufnehmen?

  1. einen Rekord für diesen Datensatz
  2. Prüfung bearbeiten Berechtigungen erhalten, Daten aus dem Datensatz
  3. Update einige Datensätze
  4. Update einige andere Aufzeichnungen

mit Sollte ich die Transaktion vor dem "get a record" -Stadium starten oder nur um die Updates herum?

Ich benutze Postgres/Django transaction.atomic(), aber ich denke nicht, dass es hier zählt.

Antwort

4

Die kurze Version: "Es kommt darauf an".

Die lange Version:

Wenn Sie eine Lese-Modifikations-Schreib-Zyklus tun, muss es dann nicht nur in einer Transaktion sein, aber Sie müssen alle Datensätze SELECT ... FOR UPDATE Sie später ändern wollen. Andernfalls riskieren Sie verlorene Schreibvorgänge, bei denen Sie ein Update überschreiben, das zwischen dem Lesen des Datensatzes und dem Schreiben des Updates erstellt wurde.

SERIALIZABLE Transaktionsisolierung kann auch dabei helfen.

Sie müssen Nebenläufigkeit und Isolation wirklich verstehen. Leider ist die einzige einfache, einfache "Just do X" Antwort, ohne es zu verstehen, jede Transaktion zu beginnen, indem alle beteiligten Tabellen gesperrt werden. Die meisten Leute wollen das nicht tun.

Ich empfehle ein Lesen (oder zwei, drei oder vier - es ist hartes Material) von the tx isolation docs. Experimentieren Sie mit gleichzeitigen psql Sitzungen (mehreren Terminals), um Wettkampfbedingungen und Konflikte zu erstellen.

+1

Ein Hinweis für Django Benutzer: SELECT ... FOR UPDATE' ist über Djangos 'select_for_update' (https://docs.djangoproject.com/en/dev/ref/models/querysets/#django.db.models.query.QuerySet.select_for_update) –

+1

@ScottStafford ... aber leider nicht 'SELECT ... FOR SHARE' oder PostgreSQL' SELECT ... FOR KEY SHARE'/'KEY UPDATE'. –

+0

Also, um sicherzustellen, dass ich mit dem Django-Winkel folge, wird ein Aufruf wie 'MyObject.objects.get (pk = 5)' nichts sperren, egal ob in einem 'transaction.atomic()' oder nicht. Um dies zu tun, muss ich beide innerhalb einer 'transaction.atomic()' UND verwenden Sie ein Formular wie 'MyObject.objects.select_for_update(). Get (pk = 5)', um sicherzustellen, dass MyObject 5 nicht geändert werden kann, bis danach Meine Transaktion endet. –

1

Idealer (wenn möglich) Sie würden tun alle Ihre vier Schritte in einem einzigendata-modifying CTE (die automatisch in einer einzigen Transaktion geschieht).

Das schließt immer noch Race Conditions aus, macht sie nur sehr unwahrscheinlich, weil der Zeitrahmen zwischen SELECT .. FOR UPDATE und einem späteren UPDATE minimiert wird. (Ja, Sie sollten immer FOR UPDATE (or another appropriate locking level) verwenden, um Race-Bedingungen unter starkem gleichzeitigen Zugriff zu begegnen.)

Dies ist nicht der typische (ineffiziente) Ansatz eines Web-Framework wie Django. Aber es ist der überlegene Ansatz. Es optimiert die Leistung in mehrfacher Hinsicht:

  • weniger Umläufe auf dem DB-Server (wahrscheinlich am wichtigsten)
  • minimieren Lock-Zeiten
  • Postgres zulassen, um Abfragen zu optimieren

Wenn SELECT .. FOR UPDATE mit Beachten Sie in einem Datenmodifizierungs-CTE, dass unreferenced CTEs are not executed at all Zeilen nicht wie vorgesehen gesperrt werden.

Code-Beispiele für die Daten modifizierende CTEs:

There are many more on SO. Try a seach.

+0

Sehr interessant. In meinem Fall kann ich mir mehrere Roundtrips für die DB leisten, im Austausch für die Code-Wartbarkeit und das Bewahren der Autorisierungslogik an einer Stelle im Code. Ich suche nach transaktionaler Korrektheit - trotz der Spiele, die der ORM hier spielt. Auch CTEs machen mir Angst. –

+0

@ScottStafford: SQL oder PL/pgSQL serverseitige Funktionen können weniger beängstigende Alternativen mit ähnlichen Vorteilen sein ... –

Verwandte Themen