Bei allen Transaktionen geht es immer wieder um dasselbe Grundproblem: Wie stelle ich sicher, dass eine Abfolge von einzelnen Operationen als ganzes, untrennbares Paket betrachtet wird. Hierbei muss sichergestellt werden, dass nicht Teile meines Gesamtpaketes erfolgreich abgeschlossen werden und andere hingegen nicht. Nur wenn alle im Paket enthaltenen Operationen erfolgreich waren, dürfen alle darin enthaltenen Operationen bestehen bleiben. Oder umgekehr formuliert: Sofern auch nur eine einzige Operation innerhalb einer gesamten Transaktion fehlgeschlagen hat, müssen alle Änderungen, welche durch irgendeine andere Operation in derselben Transaktion ausgelöst wurden, wieder rückgängig gemacht werden.
Um sicher zu gehen, dass meine Transaktion möglichst sicher und erfolgreich abgeschlossen werden kann, würde es an und für sich genügen, alle Tabellen, welche innerhalb der Transaktion angesprochen werden, vor Zugriffen anderer Transaktionen und oder Datenbank Benutzer zu schützen. Wir alle wissen, wie so etwas am einfachsten zu bewerkstelligen ist: Mit einem möglichst “grobkörnigen” Locking Mechanismus, z.B. auf Stufe Tabelle mit einem “Table” Lock.
Es ist klar, dass bei weniger grobkörnigen Locking Mechanismen, wie z.B. “Page” Locking oder “Record” Locking die Gefahren zunehmen, dass v.a. bei Transaktionen mit mehreren Einzeltransaktionen Konflikte entstehen, da die Daten, auf welche in der Transaktion zugegriffen werden soll, evtl. nicht verfügbar sind. In der Praxis haben wir es hier mit einem ausgewachsenen Zielkonflikt zu tun und wir haben abzuwägen, wie fein wir unsere Datensätze wirklich sperren wollen um unsere eigene Transaktion zu bevorzugen, ohne jedoch andere Transaktionen und Datenbank Benutzer zu stark einzuschränken. Dieser Zielkonflikt ist unvermeidlich. Würde er nicht existieren, bräuchte man sich in der Welt der Datenbank Transaktionen um einiges keine Gedanken zu machen. In Tat und Wahrheit ist es aber so, dass alle Datensätze, welche im Zusammenhang mit einer Transaktion verändert werden, für andere Benutzer oder Transaktionen nicht mehr, oder nur mit Einschränkungen (wie wir weiter unten noch sehen werden), zur Verfügung stehen. Deshalb ist es unausweichlich, sich mit den Mechanismen von Datenbank Transaktionen seriös auseinanderzusetzen.
Transaktionen müssen von sich aus schon so konzipiert sein, dass möglichst wenig Konfliktpotential entsteht, wenn Sie abgearbeitet werden. Wichtig ist in diesem Zusammenhang, dass sich Transaktionen an einige Grundregeln halten. Werden diese eingehalten, so haben wir mit einer gewissen Wahrscheinlichkeit ein erfolgreiches Betreiben der Transaktionen ermöglicht.
Die Abkürzung ACID steht hierbei für folgendes:
Atomar: Eine Transaktion soll als unzertrennbares Atom betrachtet werden, entweder als ganzes erfolgreich, oder als ganzes nicht erfolgreich. Das Atom darf quasi nicht gespalten werden.
Konsistent: Einzelne Operationen innerhalb einer Transaktion müssen bereits konsistent sein. Sie dürfen nicht bestehende Datenbank oder Business Regeln weder explizit noch implizit verletzen.
Isoliert: Das System muss noch nicht bestätigte Veränderungen einer Transaktion vor allen anderen Transaktionen schützen. Dieses Schützen erfolgt i.d.R. durch Locking der in der Transaktion beteiligten Datensätze.
Beständig: Wenn eine Transaktion bestätigt wurde, dann muss die Datenbank in der Lage sein, alle vorgenommenen Veränderungen permanent zu speichern. Im Falle eines Systemausfalles müssen diese wieder hergestellt werden können.
Der SQL Server besitzt seit der Version 7.0 neu auch die Möglichkeit des Record Lockings. In SQL Server 6.5 gab es dies nur bedingt, d.h. nur beim Einfügen von neuen Datensätzen am Ende einer Tabelle in der letzen Daten Seite.
Das Vorhandensein des Record Locking Features sollte jedoch nicht darüber hinwegtäuschen, dass es trotzdem durchaus Sinn machen kann, wenn man seitenweise lockt. Auf einer Seite sind mehrere Datensätze untergebracht und durch eine Page Lock Operation können mehrere Datensätze in Serie rascher upgedated werden als wenn dies mit einzelnen Record Locks zu erfolgen hätte.
Bei einem SQL Statement kann man mit sogenannten “Locking Hints” steuern, wie granular gelockt werden soll:
obiges SQL Statement würde alle Datensätze einzeln mir Record Locks abwickeln.
Folgende SQL Server Locking Hints gibt es:
Hint | Beschreibung |
---|---|
PAGLOCK |
Sperre die gesamte Seite (meherer Datensätze betroffen) |
ROWLOCK |
Record Locking auf Stufe Einzeldatensatz |
TABLOCK |
Table Lock. SQL Server hält diesen Lock nur solange, bis das Statement abgearbeitet ist. Nur wenn HOLDLOCK zusätzlich angegeben wird, wird der Table Lock bis ans Ende der aktuellen Transaktion beibehalten |
TABLOCKX |
Exclusiver Lock auf die Tabelle. Dieser Lock verhindert, dass andere von der Tabelle irgendwelche Daten auch nur lesen können. Kann, wie TABLOCK auch, auf das Statement beschränkt bleiben oder bis zum Ende der Transaktion ausgedehnt werden. |
Der SQL Server unterscheidet zwischen “Write” Locks und “Read” Locks. Die “Write” Locks werden auch als “Exclusive” Locks bezeichnet und die “Read” Locks auch als sogenannte “Shared” Locks. Ein “Write” Lock ist mit einem anderen “Write” Lock im Konflikt, und auch mit anderen “Read” Locks. “Read” Locks hingegen sind grundsätzlich mit anderen “Read” Locks nicht im Konflikt. Es ist aber wichtig zu verstehen, dass eine Transaktion keinen “Write” Lock erhalten kann, wenn gerade ein “Read” Lock offen ist. Diese fundamentale Eigenschaft des SQL Servers stellt sicher, dass Daten, welche gerade in einer Transaktion gelesen werden, nicht gleichzeitig verändert werden können.
Die alles entscheidende Frage ist nun: “Wenn ein Lesevorgang innerhalb einer Transaktion einen “Read” Lock absetzt, wie lange soll dieser Lock bestehen bleiben?” Je länger ein “Read” Lock bestehen bleibt, desto eher ist meine eigene Transaktion “isoliert” von anderen. Als Nachteil erweist sich natürlich einmal mehr die Verfügbarkeit des Systems für andere Benutzer.
Genau aus dem Grund ist es möglich, selber zu beeinflussen, welchen “Isolation” Level man bzgl. der Locks wünscht:
Isolation Level | Description |
---|---|
Read |
Die Transaktion kann alle Daten lesen, unabhängig davon, ob nun ausstehende “Write” Locks vorhanden sind oder nicht. Zusätzlich wird die Transaktion in diesem Modus keine eigenen “Read” Locks absetzen. Dieser Isolation Level bringt natürlich die beste Performance ist aber anfälliger auf Dateninkonsistenz denn dieser Isolation Level ist der am wenigsten Restriktive und kann zu sogenannten “Dirty Reads” führen, d.h. dass Daten, welche eigentlich “nicht stimmig sind” und von anderen Transaktionen noch nicht bestätigt wurden (und deshalb evtl wieder zurückgestellt werden!) trotzdem gelesen werden können! |
Read Committed |
Hier kann die Transaktion nur Daten lesen, welche bereits “committed“ wurden, d.h. es muss gewartet werden, bis offene “Write” Locks abgeschlossen sind. Für das Lesen von Daten werden neue “Read” Locks abgesetzt, welche aber bereits nach Abschluss des SQL Statements (d.h. vor Ende der Transaktion!) wieder freigegeben werden. Es ist nicht sichergestellt, dass ein erneutes Lesen derselben Daten innerhalb der Transaktion zu demselben Resultat führt. Dieser Isolation Level ist die Standardeinstellung für ADO und via VFP(!) wenn NICHT aus MTS heraus betrieben! |
Repeatable Read |
Analog zu “Read Committed” mit der Ausnahme, dass die “Read” Locks bis ans Ende der Transaktion aufrecht erhalten werden. SQL Server 6.5 unterstützt diesen Level nicht, er stellt in einem solchen Fall auf “Serializable” um. Beim Isolation Level “Repeatable Read” werden alle in einer Query bezogenen Daten gelockt (“Read” bzw. “Write” Locks) aber andere Benutzer und Transaktionen können neue Datensätze in die betroffenen Tabellen weiterhin einfügen, weshalb ein erneutes Lesen innerhalb der Transaktion diese neuen Datensätze sehen würde. |
Serializable |
Analog zu “Repeatable Read” wobei zusätzlich sichergestellt wird, dass selbst bei mehrmaligem Ausführen von ein und demselben SQL Statement immer dieselben Resultate gesehen werden. Es wird verhindert, dass neue Datensätze angelegt werden (dass bestehende nicht entfernt werden können, wird ja bereits durch die Locks sichergestellt), solange die Transaktion läuft. Dies wird durch “Table” oder “Index” Locks sichergestellt. Dieser Isolation Level ist der Standard bei allen MTS Komponenten! |
Das Setzen des Transaction Isolation Levels erfolgt mit folgender TSQL Anweisung, welche pro Connection via SQL Pass Through gesetzt werden kann:
Für diejenigen, welche in die n-Tier Entwicklung unter MTS einsteigen, ist es wichtig zu verstehen, dass für das Sicherstellen von Transaktionen aus dem MTS heraus der DTS (Distributed Transaction Coordinator) verwendet wird. DTS wird auf den Maschinen, auf welchen der entsprechende SQL Server läuft, als Service betrieben. Zusammen mit der oben gewonnenen Erkenntnis, dass der Isolation Level “Serializable” der Standard für alle Transaktionen aus MTS heraus ist, ist sofort klar, dass Performance Aspekte im MTS Umfeld sehr wohl ein Thema sind, ist doch der Isolation Level “Serializable” derjenige der am “verschwenderischsten” mit der Verfügbarkeit umgeht oder anders gesagt, auf der vorsichtigen Seite operiert, was die eigene Transaktionssicherheit angeht, den anderen Benutzern und Transaktionen aber entsprechend weniger Spielraum lässt.