Net-Base Revistë

08.10.2026

FireDAC: Kapsulim i pastër i transaksioneve – përsëritje në rast të deadlock-ëve dhe rollback i konsistent pa pasoja anësore

Si të kapsullosh transaksionet FireDAC në mënyrë që deadlock-et (p.sh. SQL Server 1205) të mos shkaktojnë pasoja anësore: Retry për të gjithë Unit-of-Work, Rollback konsekvent, pozicionim korrekt i Savepoints, plus udhëzime për Debugging dhe për operim.

08.10.2026

Nga tema e revistës në praktikën e projektit

Faqe shërbimi dhe teknike të përshtatshme për artikullin

Leserfrage: „Wir sehen sporadisch Deadlocks im Log (SQL Server 1205). Wie kann ich BDE-Ablosung mit nativer Anbindung Transaktionen sauber kapseln, damit ein Retry nicht neue Fehler produziert – und ein Rollback wirklich ‚alles‘ zurücksetzt, ohne dass mein Code in einem halben Zustand weiterläuft?“

Vorläufige Antwort: Lege den Retry nicht um ein einzelnes Statement, sondern um die komplette fachliche Unit-of-Work. Kapsle StartTransaction/Commit/Rollback an genau einer Stelle, rolle bei definierten transienten Konkurrenzfehlern konsequent zurück, setze alle beteiligten Objekte pro Versuch deterministisch neu auf (Queries, Parameter, Datasets, In-Memory-Modelle) und verschiebe Nebenwirkungen außerhalb der Datenbank (Mail/Queue/REST) hinter den Commit oder in eine Outbox.

FireDAC Transaktionen sauber kapseln: Warum Deadlocks so oft „Nebenwirkungen“ verursachen

Textfreie Ablaufgrafik eines Transaktions-Retry-Loops mit Backoff und Jitter
Technisches Detailbild: Retry-Schleife mit Backoff als Ablauf, nicht als Code.
Ausgedruckte Diagramme zu Lock-Reihenfolgen werden auf einem Schreibtisch markiert, als Vorbereitung zur Deadlock-Analyse
Betriebsnahes Debug-Motiv: Analyse von konkurrierenden Transaktionen und Lock-Reihenfolgen.
Abstraktes Diagramm einer Datenbanktransaktion mit zwei Savepoints und kompletter Rücksetzung bei Deadlock
Vergleich: äußere Transaktion vs. Savepoint und was bei Deadlock-Victim passiert.

Die typische Situation: Ein Service verarbeitet Jobs parallel. Oder eine Desktop-Anwendung hat mehrere Nutzer, die „zufällig gleichzeitig“ dieselben Datensätze anfassen. Alles läuft wochenlang stabil – und dann knallt es unter Last. Nicht als sauberer, reproduzierbarer Fehler, sondern als sporadische Ausnahme: „Deadlock-Victim“. Danach wirkt es, als hätte der Code „weitergemacht“, aber Daten fehlen. Oder schlimmer: außen wurde schon etwas ausgelöst, innen wurde zurückgerollt.

Der Grund ist nicht mystisch, sondern mechanisch: SQL Server löst einen Deadlock serverseitig, indem er eine Transaktion als „Victim“ auswählt, diese abbricht und zurückrollt; der Client bekommt Fehler 1205.[Quelle] Das ist für den Datenbankbetrieb sinnvoll (Locks werden frei, andere Transaktionen laufen weiter). Für Anwendungscode ist es gefährlich, wenn Transaktionsgrenzen diffus sind oder wenn der Fehlerpfad nicht strikt „abklemmt“.

„Rollback ohne Nebenwirkungen“ heißt deshalb nicht nur: Die DB ist wieder sauber. Es heißt: Kein Dataset hängt im Edit-Status, kein Objektmodell glaubt an eine ID, die nie committed wurde, und kein externer Effekt wurde doppelt ausgelöst. Genau dafür brauchst du Kapselung und ein klares Retry-Design.

FireDAC-Transaktionen im Kern: Auto-Commit, explizite Grenzen, Savepoints

BDE-Ablosung mit nativer Anbindung arbeitet standardmäßig im Auto-Commit-Modus: Jedes Statement ist implizit seine eigene Transaktion. Sobald du aber mehrere Statements fachlich zusammenziehen musst, brauchst du explizite Grenzen mit StartTransaction/Commit/Rollback.[Quelle] Das ist der Teil, den alle kennen – und trotzdem geht es im Alltag schief, weil diese Grenzen in großen Codebasen „wandern“.

Eine wichtige FireDAC-Eigenschaft für Kapselung: Verschachtelte StartTransaction-Aufrufe werden nicht als neue DB-Transaktion umgesetzt, sondern als Savepoints. Der erste Aufruf startet die Transaktion, weitere Aufrufe setzen Savepoints, und ein Rollback auf innerer Ebene rollt dann nur bis zum Savepoint zurück.[Quelle] Das kann helfen – oder Chaos stiften. Entscheidend ist, ob ihr im Team klar definiert, wer Transaktionen besitzt und wer nur „innerhalb“ arbeitet.

Zwei Anti-Patterns, die Deadlock-Retry fast immer ruinieren

  1. Transaktionen „zur Sicherheit“ überall starten: Jede Methode macht StartTransaction, weil man „nicht weiß“, ob schon eine läuft. Ergebnis: Savepoints stapeln sich, Fehlpfade werden unklar, Rollback-Semantik wird geraten statt verstanden.
  2. Retry ohne Reset von Objektzuständen: Nach einem Deadlock wird der Codeblock erneut aufgerufen, aber Queries, Parameter, Datasets oder In-Memory-Listen werden wiederverwendet. Damit wiederholst du nicht die Unit-of-Work, sondern einen Zustand mit Vergangenheit.

Deadlock-Retry: Warum „ganze Transaktion wiederholen“ die richtige Linie ist

Für SQL Server ist die Richtung klar: Bei Deadlocks (1205) wird explizit empfohlen, die gesamte Transaktion erneut auszuführen, nicht nur ein einzelnes Statement.[Quelle] Der praktische Hintergrund: Innerhalb eines mehrstufigen Ablaufs können Reihenfolgen, Zwischenzustände oder abhängige Änderungen entstehen. Ein Statement-Retry an „der Stelle, wo es gerade knallt“ ist oft logisch falsch, weil du nicht mehr sicher weißt, welche Vorarbeit in welcher Form noch gilt.

Übertragen auf Delphi/FireDAC: Der Retry-Wrapper gehört außen um die Unit-of-Work. Und die Unit-of-Work ist nicht „ein Update“, sondern der fachliche Block, der atomar sein muss: z. B. „Beleg anlegen + Positionen + Bestand reservieren + Status setzen“.

Welche Fehler du überhaupt retryen solltest (und welche nicht)

Retry ist nur dann sinnvoll, wenn der Fehler transient ist: eine kurzlebige Konkurrenz- oder Ressourcensituation, die beim nächsten Versuch plausibel verschwunden ist. Microsoft nennt im Kontext der Provider-Retry-Logik u. a. Deadlocks und weitere Sperr-/Konkurrenzfehler als Kandidaten, bei denen „Rollback + kurzer Backoff + Retry der gesamten Transaktion“ ein valides Muster ist – und weist gleichzeitig darauf hin, dass häufiges Auftreten ein Hinweis auf strukturelle Probleme sein kann (Contention, Tuning, Skalierung).[Quelle]

Nicht retrybar sind typischerweise: fachliche Validierungen, Constraint-Verletzungen, SQL-Syntaxfehler, fehlende Rechte oder „Objekt existiert nicht“. Das sind deterministische Fehler – ein Retry erhöht nur Last und verschleiert Ursachen.

Der saubere Ansatz: ein Transaktions-Wrapper, der wirklich kapselt

„Sauber kapseln“ ist weniger eine Frage des einen perfekten Helpers als eine Frage von Garantien. Wenn du einen Wrapper baust, sollte er drei Dinge erzwingen:

  • Atomarität: Alles committed oder alles rollbackt – ohne „halbe“ Erfolge.
  • Determinismus pro Versuch: Jeder Retry startet mit definierten Objekten und definiertem Zustand.
  • Kontrolliertes Verhalten bei Fehlern: Retry nur für definierte Fehler, begrenzt, mit Backoff und mit Logs, die Betrieb und Entwicklung nutzen können.

Nummerierte Schrittfolge für die Implementierung (ohne Framework-Fetisch)

  1. Unit-of-Work schneiden: Schreibe auf, was fachlich zusammengehört. Wenn du es nicht in einem Satz erklären kannst, ist es meist schon zu groß.
  2. Transaktionsbesitz festlegen: Genau eine Schicht startet/committed/rollbackt (z. B. Service-Layer). Unterfunktionen dürfen keine Commits auslösen.
  3. Retry-Schleife außerhalb: Die Schleife umfasst den kompletten Ablauf. Innerhalb gibt es genau einen StartTransaction und genau einen Commit oder Rollback.
  4. Pro Versuch Ressourcen neu aufbauen: Queries/Commands, Parameterwerte, temporäre Collections. Wenn Wiederverwendung, dann mit hartem Reset (SQL, Params, Prepared-State, Dataset-State).
  5. Rollback robust machen: Rollback gehört in den Exception-Pfad. Und: Rollback-Fehler dürfen den ursprünglichen Fehler nicht „überdecken“; sie gehören ins Log, aber die Ursache bleibt sichtbar.
  6. Backoff + Jitter: Eine kleine, variierende Pause verhindert, dass parallele Worker im Gleichschritt wieder kollidieren.
  7. Abbruchkriterien: Max. Versuche und/oder maximale Gesamtdauer. In Services: Abbruch bei Shutdown/Cancellation sauber unterstützen.
  8. Beobachtbarkeit: Pro Versuch loggen: Workflow, Versuchszähler, Fehlercode (NativeError), Dauer seit StartTransaction, Connection/DB-Kontext.

Rollback ohne Nebenwirkungen: die typischen Fallen in FireDAC-Projekten

Viele Teams unterschätzen, dass ein DB-Rollback nicht automatisch den Zustand auf der Delphi-Seite repariert. Drei Felder tauchen immer wieder auf – unabhängig davon, ob du Desktop, Service oder REST-Server baust.

1) Datasets mit „pending state“ (CachedUpdates, Edit-Modus, offene Cursor)

Wenn Datasets Änderungen puffern (z. B. über CachedUpdates) oder wenn sie im Edit/Insert-Zustand bleiben, dann ist „Rollback“ zwar in der DB passiert, aber die nächste Aktion im Code arbeitet weiter mit alten Puffern. Bei einem Retry kann das bedeuten: dieselben Änderungen werden erneut gesendet – oder es knallt an anderer Stelle mit Folgefehlern, die niemand mehr mit dem Deadlock verknüpft.

Praktische Konsequenz: Für transaktionskritische Schreibpfade sind klar begrenzte Command/Query-Objekte pro Versuch oft einfacher zu kontrollieren als lange offene Datasets. Wenn Datasets unvermeidbar sind, dann gehört ein explizites Reset in den Retry-Pfad (nicht als „vielleicht“, sondern als Regel).

2) Vorweggenommene IDs und Objektzustände

Nach einem Rollback muss dein Objektmodell so behandelt werden, als sei nie gespeichert worden. Das ist der Punkt, an dem sich „Retry klingt einfach“ in Realität verwandelt: Du hast vielleicht schon IDs ausgelesen, Beziehungen gesetzt, UI-Status aktualisiert oder Folgeoperationen geplant.

Wenn du clientseitige IDs verwendest (z. B. GUIDs), kann das Idempotenz vereinfachen, weil du denselben Schlüssel im Retry erneut verwenden kannst. Wenn du serverseitige Identity/Sequenzen nutzt, musst du sauber trennen: Eine „gemerkte“ ID ist erst nach Commit belastbar. Alles davor ist Vorschau, nicht Wahrheit.

3) Nebenwirkungen außerhalb der Datenbank (Queue, REST, Dateien, Mail)

Das ist der große Stolperdraht: Die Datenbank kann keine E-Mail zurückrollen und keine Message aus einer Queue „ungepostet“ machen. Wenn du in der Unit-of-Work externe Effekte auslöst, produziert ein Retry schnell Duplikate oder fachliche Inkonsistenzen.

Typische Auswege, die im Betrieb funktionieren:

  • Outbox-Pattern: Du schreibst die „zu sendende Aktion“ in eine DB-Tabelle innerhalb derselben Transaktion und lässt einen separaten Worker nach Commit senden.
  • Post-Commit-Aktionen: Externe Calls passieren erst, wenn der Commit durch ist.
  • Idempotency-Key: Wenn externe Systeme unterstützt werden müssen, nutze eindeutige Schlüssel, damit Wiederholungen nicht doppelt wirken.

Ohne so eine Entscheidung ist Deadlock-Retry zwar „DB-sauber“, aber außenwelt-unsauber. Und außen merkt es zuerst der Fachbereich.

Savepoints in FireDAC: sinnvoll, aber kein Deadlock-Heilmittel

Savepoints (in FireDAC über verschachtelte StartTransaction-Aufrufe) sind nützlich, wenn du Teilbereiche bewusst isolieren willst: „Dieser Subschritt darf fehlschlagen und zurückgerollt werden, ohne dass der ganze Ablauf scheitert.“ FireDAC dokumentiert das Savepoint-Verhalten ausdrücklich.[Quelle]

Für Deadlocks gilt aber: Wenn die Datenbank deine Transaktion als Victim abbricht, ist die gesamte Transaktion weg. Dann helfen Savepoints nicht mehr – du musst ohnehin von außen neu starten. Savepoints sind also ein Werkzeug für interne Fehlerbehandlung, nicht die Grundlage für Deadlock-Retry.

Problemstellung Äußere Transaktion (Start/Commit/Rollback) Savepoint (FireDAC verschachtelt) Konsequenz für Retry
Fachlicher Ablauf muss atomar sein Ja, passend Nur innerhalb der äußeren Transaktion Retry gehört um die äußere Unit-of-Work
Optionaler Subschritt darf zurückrollen Nur grob möglich Ja, präzise Hilft, aber ersetzt keinen Deadlock-Retry
Deadlock-Victim: DB bricht Transaktion ab DB rollt vollständig zurück Verliert Bedeutung Neuer Versuch muss komplett neu starten
Wartbarkeit im Team Gut, wenn zentral Riskant bei Wildwuchs Regeln zu „Transaktionsbesitz“ sind entscheidend

Konstruiertes Beispiel: Retry eingebaut – und plötzlich doppelte Außenwirkung

Deadlocks greifbar machen: Debugging- und Betriebshebel

Fehlerklassifizierung statt „catch all“

Wenn du Retries einbaust, muss klar sein, warum du retryest. Für SQL Server ist der Deadlock-Fehler 1205 die saubere Unterscheidung: Die Transaktion wurde serverseitig abgebrochen und zurückgerollt.[Quelle] Alles andere ist ein anderer Fehlerfall, auch wenn er „ähnlich klingt“.

Deadlock-Graphen nutzen, wenn Häufigkeit steigt

Wenn Deadlocks mehr als „selten“ auftreten, ist Retry nicht mehr nur Absicherung, sondern Lastverstärker. Dann brauchst du Ursachenanalyse. SQL Server kann Deadlocks als Graph/Report bereitstellen (z. B. über Extended Events). Das ist kein Delphi-Detail, aber die schnellste Abkürzung: Du siehst, welche Statements und Objekte (Indizes, Tabellen) sich gegenseitig blockieren und welche Reihenfolge beteiligt ist. Die praktische Folge ist oft banal: Update-Reihenfolgen vereinheitlichen, Indizes ergänzen, Transaktionsdauer verkürzen.

Transaktionsdauer messen: der unterschätzte Indikator

Deadlocks und Lock-Waits werden wahrscheinlicher, je länger Transaktionen Locks halten. Miss deshalb im Code pro Versuch die Zeit von StartTransaction bis Commit/Rollback. Wenn du dabei siehst, dass innerhalb der Transaktion noch Dateizugriffe, HTTP-Calls oder UI-nahe Wartezeiten liegen, ist der nächste Schritt klar: Transaktion enger schneiden, Außenwelt hinter Commit.

Wann sich die Kapsel wirklich lohnt – und wann simpler besser ist

Ein sauberer Transaktions- und Retry-Wrapper kostet Strukturarbeit. Er lohnt sich besonders, wenn:

  • mehrere Statements fachlich zusammengehören (mehrstufige Writes),
  • Parallelität real ist (mehrere Worker, mehrere Nutzer, Jobs plus UI),
  • Services/Batch-Jobs laufen, bei denen „einfach abbrechen“ nicht akzeptabel ist,
  • Nebenwirkungen nach außen existieren und du sie commit-gekoppelt steuern musst (Outbox).

Wenn du dagegen nur ein einzelnes Statement hast (Auto-Commit) und der Benutzer bewusst erneut anstoßen kann, ist ein technischer Retry oft nicht der erste Hebel. Dort reicht häufig: Fehler sauber anzeigen, Kontext loggen, und die Datenbankseite so gestalten, dass Konflikte seltener werden (Indexe, kurze Statements, klare Sperrstrategie).

Teamregeln, die „Transaktions-Suppe“ verhindern

Die langlebige Lösung ist fast immer: Regeln, nicht Magie. Ein praxistaugliches Set:

  • Eine Stelle besitzt die Transaktion: Start/Commit/Rollback sind nicht quer über den Code verteilt.
  • Retry ist zentral: Nicht jede Datenzugriffsmethode baut eigene Schleifen. Ein Verhalten, ein Logformat.
  • Unterfunktionen committen nicht: Sie liefern Erfolg/Fehler zurück, aber sie entscheiden nicht über Commit.
  • Nach Rollback kein „Weiter so“: Der Fehlerpfad beendet die Unit-of-Work, setzt Zustände zurück und startet höchstens den nächsten Versuch.
  • Idempotenz wird bewusst gemacht: Wo Wiederholung möglich sein muss, werden Schlüssel/Outbox/Inbox früh eingeplant.

Umsetzbare Priorität: was du als Erstes anfassen solltest

Wenn du morgen im Bestandssystem anfängst und nicht alles auf einmal umbauen willst, bringt diese Reihenfolge schnell Stabilität:

  1. Transaktionsgrenze pro Workflow definieren und zentralisieren: Eine Unit-of-Work, eine Kapsel, klare Besitzverhältnisse.
  2. Retry nur für klar transiente Konkurrenzfehler: Zuerst Deadlock (1205) und Lock-Timeouts (je nach System), mit begrenzten Versuchen und Backoff/Jitter; jeden Versuch mit Code und Dauer loggen.
  3. Zustandsreset pro Versuch erzwingen: Query/Command-Objekte, Parameter und In-Memory-Modelle pro Versuch neu oder garantiert „clean“.
  4. Externe Nebenwirkungen hinter Commit ziehen: Outbox oder Post-Commit-Aktionen, bevor du „mehr Retry“ baust.

Wenn du dafür Sparring zu Architektur, Transaktionsschnitt und Betriebssicherheit in Delphi/FireDAC brauchst, ist ein kurzer Abgleich oft der schnellste Weg zur sauberen Linie: Kontakt zu Net-Base aufnehmen.

Quellen und weiterfuehrende Informationen

Die fachlichen Kernaussagen wurden anhand der folgenden externen Quellen redaktionell eingeordnet.

  1. Deadlocks Guide – SQL Server | Microsoft Learn (learn.microsoft.com)
    SQL Server löst Deadlocks serverseitig durch Wahl eines Deadlock Victim und rollt dessen Transaktion zurück (Fehler 1205).
  2. Built-In Retry Logic Providers in SqlClient – ADO.NET Provider for SQL Server | Microsoft Learn (learn.microsoft.com)
    Bei Deadlocks und ähnlichen transienten Sperr-/Konkurrenzfehlern soll die gesamte Transaktion nach Rollback erneut ausgeführt werden, nicht nur ein einzelnes Statement.
  3. Managing Transactions (FireDAC) – RAD Studio (docwiki.embarcadero.com)
    FireDAC arbeitet standardmäßig im Auto-Commit-Modus und bietet StartTransaction/Commit/Rollback für explizite Transaktionsgrenzen; verschachtelte Transaktionen werden über Savepoints abgebildet.

Für dieses Thema sind auch FireDAC Transaktion Kapseln und Deadlock Retry Delphi wichtig. Der Beitrag ordnet diese Aspekte verständlich ein und zeigt, worauf es im Alltag ankommt.

Projekt oder Modernisierungsvorhaben mit Net-Base besprechen.

Hapi tjetër

Kur nga një temë lind një projekt real, arkitektura, sistemi ekzistues dhe operimi duhet të vlerësohen së bashku që në fillim.

Ne nuk mbështesim vetëm në çështje të veçanta, por edhe kur nga fragmente të kodit burimor, temat legacy ose idetë për portale duhet të zhvillohen në një projekt korporativ të qëndrueshëm.

  • Gjendja ekzistuese, imazhi i synuar dhe rreziqet teknike vlerësohen së bashku.
  • REST, qasja në të dhëna, portalet dhe implementimi nuk shtyhen si pasojë e mëvonshme.
  • Ju e shihni herët se cila rrugë është e qëndrueshme ekonomikisht dhe operativisht.

Ndaje postimin

Shpërndaj këtë postim drejtpërdrejt

LinkedIn, X, XING, Facebook, WhatsApp dhe E-Mail janë menjëherë të disponueshme. Për Instagram po përgatisim lidhjen dhe tekstin e shkurtër.

Postë elektronike

Instagram hapet në një skedë të re. Linku dhe teksti i shkurtër kopjohen më parë në memorjen e kopjimit.

Shkruaj koment

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert