Implementierung der gleichzeitigen Steuerung mit ORM - Eine eingehende Betrachtung von pessimistischen und optimistischen Sperrungen
Daniel Hayes
Full-Stack Engineer · Leapcell

Einleitung
In den heutigen stark gleichzeitigen Anwendungen ist die Gewährleistung der Datenintegrität, wenn mehrere Benutzer oder Prozesse versuchen, dieselben Daten gleichzeitig zu ändern, von größter Bedeutung. Ohne entsprechende Mechanismen können Race Conditions zu beschädigten Daten, inkonsistenten Zuständen und letztlich zu einem Vertrauensverlust im System führen. Datenbank-Sperrstrategien sind der Eckpfeiler für die Bewältigung dieser Herausforderungen und bieten einen systematischen Weg zur Verwaltung des gleichzeitigen Zugriffs. Während herkömmliches SQL leistungsstarke Sperrprimitiven bietet, kann die direkte Interaktion mit ihnen umständlich sein, insbesondere innerhalb von Object-Relational Mapping (ORM)-Frameworks. Dieser Artikel befasst sich damit, wie ORMs es Entwicklern ermöglichen, entscheidende Sperrstrategien zu implementieren – insbesondere pessimistische Sperrung (mittels SELECT FOR UPDATE
) und optimistische Sperrung (durch Versionierung) – und so ihre Anwendungsentwicklung zu rationalisieren und gleichzeitig die Datenkonsistenz zu wahren. Das Verständnis dieser Techniken ist nicht nur eine akademische Übung; es ist eine praktische Notwendigkeit für den Aufbau robuster und skalierbarer Anwendungen.
Kernkonzepte der gleichzeitigen Steuerung
Bevor wir uns mit den Implementierungsdetails befassen, wollen wir die Kernkonzepte rund um die gleichzeitige Datenmodifikation und die beiden primären Sperrstrategien, die wir diskutieren werden, klar verstehen.
Gleichzeitige Steuerung (Concurrency Control): Dies bezieht sich auf die Methoden, die zur Verwaltung des gleichzeitigen Zugriffs auf gemeinsam genutzte Daten verwendet werden. Ziel ist es, mehreren Transaktionen zu ermöglichen, gleichzeitig ausgeführt zu werden und gleichzeitig sicherzustellen, dass die Datenbank in einem konsistenten Zustand bleibt und die Ergebnisse gleichzeitiger Transaktionen korrekt sind.
Transaktion (Transaction): Eine Transaktion ist eine einzelne logische Arbeitseinheit, die auf den Inhalt einer Datenbank zugreift und ihn möglicherweise modifiziert. Transaktionen sind durch ACID-Eigenschaften (Atomicity, Consistency, Isolation, Durability) gekennzeichnet, die eine zuverlässige Verarbeitung von Datenbankoperationen garantieren.
Race Condition: Eine Race Condition tritt auf, wenn mehrere Operationen gleichzeitig ausgeführt werden und das Endergebnis von der spezifischen Reihenfolge abhängt, in der diese Operationen ausgeführt werden. In Bezug auf Datenbanken bedeutet dies oft, dass zwei Transaktionen dieselben Daten lesen und dann versuchen, sie zu aktualisieren, was dazu führt, dass eine Aktualisierung die andere überschreibt oder zu einem inkonsistenten Zustand führt.
Pessimistische Sperrung (Pessimistic Locking): Diese Strategie geht davon aus, dass Konflikte häufig auftreten, und verhindert daher den gleichzeitigen Zugriff auf Daten, indem sie diese sofort vor der Modifikation sperrt. Sobald Daten gesperrt sind, werden andere Transaktionen daran gehindert, auf die Daten zuzugreifen, bis die Sperre aufgehoben wird. Dieser Ansatz gewährleistet die Datenintegrität, kann jedoch die Gleichzeitigkeit verringern und bei unsachgemäßer Verwaltung zu Deadlocks führen.
Optimistische Sperrung (Optimistic Locking): Diese Strategie geht davon aus, dass Konflikte selten sind. Anstatt Daten im Voraus zu sperren, erlaubt sie den gleichzeitigen Zugriff und prüft erst beim Speichern der Änderungen auf Konflikte. Wenn ein Konflikt erkannt wird (d.h. die Daten wurden von einer anderen Transaktion geändert, seit sie zuletzt gelesen wurden), wird die Transaktion typischerweise zurückgerollt und der Benutzer aufgefordert, es erneut zu versuchen. Dieser Ansatz maximiert die Gleichzeitigkeit, erfordert jedoch von den Entwicklern, potenzielle Konflikte zu handhaben.
Pessimistische Sperrung mit ORMs (SELECT FOR UPDATE)
Pessimistische Sperrung, insbesondere mittels SELECT FOR UPDATE
, ist ideal für Situationen, in denen die Datenintegrität von größter Bedeutung ist und gleichzeitige Modifikationen sehr wahrscheinlich sind oder wenn komplexe Entscheidungen auf der Grundlage des aktuellen Datenzustands erforderlich sind.
Prinzip
Wenn eine SELECT FOR UPDATE
-Abfrage ausgeführt wird, sperrt die Datenbank die ausgewählten Zeilen. Jede andere Transaktion, die versucht, diese gesperrten Zeilen zu lesen oder zu aktualisieren, wartet entweder, bis die Sperre aufgehoben wird, oder erhält eine Fehlermeldung, abhängig vom Isolationsgrad und den Gleichzeitigkeits-Einstellungen der Datenbank. Die Sperre wird gehalten, bis die Transaktion, die sie erworben hat, committet oder zurückgerollt wird.
ORM-Implementierungsbeispiel
Die meisten ORMs bieten eine einfache Möglichkeit, SELECT FOR UPDATE
zu integrieren. Betrachten wir ein Beispiel mit einer hypothetischen Product
-Entität und einem Szenario, in dem wir den Lagerbestand verringern müssen, aber nur, wenn der Lagerbestand über einem bestimmten Schwellenwert liegt.
# Django ORM Beispiel from django.db import transaction from .models import Product def decrement_product_stock_pessimistic(product_id: int, quantity: int): with transaction.atomic(): # Wählt das Produkt FOR UPDATE aus und sperrt es für diese Transaktion product = Product.objects.select_for_update().get(id=product_id) if product.stock >= quantity: product.stock -= quantity product.save() print(f