Das Zwei-Phasen-Commit-Protokoll (2PC) definiert ein Protokoll, bei dem ein Koordinator und mehrere Datenquellen innerhalb einer globalen Transaktion angesprochen werden können. Teilnehmen an globalen Transaktionen können alle Datenquellen, die das Protokoll unterstützen. Unterstützt wird das Protokoll vom MySQL Server seit der Version 5.0. Oracle, und IBMs DB2 können ebenso eingebunden werden wie viele andere Datenquellen, zum Beispiel Applikationen, wenn diese das Protokoll implementieren. Das Protokoll ist im Dokument „Distributed Transaction Processing: The XA Specification“ der X/Open Group [1] spezifiziert. Egal, ob nun die Flugesellschaft eine Oracle-Datenbank und die Hotelvermittlung eine MySQL-Datenbank einsetzt, mit dem 2PC-Protokoll könnten beide Datenbanken friedlich miteinander vereint an einer globalen Transaktion teilnehmen. Oracle reserviert einen Flug, MySQL sucht das passende Hotel, eine Java-Anwendung bucht die Zugverbindung, und ich freue mich, dass ich mit einem Bezahlvorgang die Flucht in den Frühling antreten kann. Es gibt noch viel mehr Anwendungsbeispiele für verteilte Transaktionen als dieses egoistische, wenngleich reale Beispiel.
Verteilte Transaktionen sind keine Ausnahme
Während einer Datenbankmigration könnten Sie gezwungen sein, mit zwei Systemen zu arbeiten. Angenommen, jedes der Systeme enthält einen Teil der Daten, und Sie möchten mit einer Transaktion alle Daten aktualisieren. Werden die Datenbestände einzeln nacheinander mittels zweier lokaler Transaktionen aktualisiert, dann könnte ein System die Änderungen erfolgreich durchführen (COMMIT), während im anderen System ein Fehler auftritt und die Änderung misslingt (ROLLBACK). Obwohl die zwei Systeme jeweils ACID-konforme Transaktionen durchgeführt haben, ist das Ergebnis der globalen Transaktion nicht ACID-konform. ACID steht für die englischen Begriffe Atomicy (Atomarität), Consistency (Konsistenz), Isolation (Isolation) und Durability (Dauerhaftigkeit) [2]. Aus Sicht der globalen Transaktion ist die Forderung nach Atomarität verletzt. Atomarität verlangt, dass eine Transaktion entweder ganz oder gar nicht ausgeführt wird. Eine nur teilweise Ausführung der zu einer Transaktion gehörenden Operationen ist kein gültiges Ergebnis. Jedes der im Beispiel erwähnten Systeme hat seine lokale Transaktion entweder vollständig abgeschlossen oder keinerlei Änderungen vorgenommen und die Forderung erfüllt. Aus der Sicht der globalen Transaktion ist jedoch nur ein Teil der Operationen erfolgreich durchgeführt worden. Ein System hat ein ROLLBACK ausgeführt, damit ist globale Transaktion nicht ACID-kompatibel, sie muss als gescheitert bezeichnet werden, und eine besondere Fehlerbehandlung seitens der Applikation ist einzuleiten.Wie das Zwei-Phasen-Commit-Protokoll die Ausfallsicherheit erhöhen kann
Noch ein Beispiel, bei dem globale Transaktionen helfen: MySQL-Cluster. Zur Steigerung der Ausfallsicherheit und zur Erhöhung der Ausführungsgeschwindigkeit werden beim MySQL-Cluster mehrere Rechner zu einem Verbund zusammengeschlossen. Je mehr Rechner mit einem Datenbestand versorgt werden, desto geringer ist die Ausfallwahrscheinlichkeit des Gesamtsystems. Wenn Sie annehmen, dass ein Rechner eine Ausfallwahrscheinlichkeit von einem Prozent hat, dann können Sie mit einem zusätzlichen Spiegelsystem der gleichen Zuverlässigkeit eine statistische Ausfallwahrscheinlichkeit von 0,01 Prozent erreichen. Die Wahrscheinlichkeit des Ausfalls des Spiegelsystems während des Ausfalls des Primärsystems beträgt ein Prozent. Statistisch gesehen wird nur in einem von 100 Fällen das Spiegelsystem genau dann ausfallen, wenn das Primärsystem nicht zur Verfügung steht: 0,01 * 0,01 = 0,0001. Auch wenn Murphy warnt, dass alle Festplatten aller Rechner aus einer Charge stammen und gleichzeitig ausfallen, so zeigt die Wahrscheinlichkeitsrechnung dennoch, dass die statistische Ausfallwahrscheinlichkeit sinkt. Bei der redundanten Datenspeicherung auf mehreren Rechnern steht der MySQL-Cluster jedoch vor dem gleichen Problem der ACID-Kompatibilität, wie es am Beispiel der Migration erklärt wurde. Die Lösung: Der MySQL-Cluster verwendet intern das Zwei-Phasen-Commit-Protokoll (2PC).Das Zwei-Phasen-Commit-Protokoll (2PC) für verteilte Transaktionen
Das 2PC-Protokoll beschreibt, wie ein Koordinator (Transaction Manager) und mindestens ein weiterer Teilnehmer (Resource Manager) eine globale Transaktion durchführen und dabei ACID-Konformität erzielen können. Wie am Namen des Protokolls erkenntlich ist, besteht es aus zwei Phasen: der Wahlphase und der Entscheidungsphase. Das Protokoll beginnt mit der Wahlphase. Der Koordinator befragt alle Teilnehmer, ob sie die ihnen angewiesene lokale Transaktion erfolgreich durchführen könnten. Die Entscheidung eines Teilnehmers, ob ein COMMIT ausgeführt werden könnte, kann nicht mehr vom Teilnehmer revidiert werden. Der Teilnehmer muss vor einer positiven Antwort alle notwendigen Vorkehrungen getroffen haben, damit ein Commit selbst nach einem Systemausfall durchgeführt werden könnte, wenn der Koordinator die entsprechende Anweisung gibt. Zur Erreichung dieser Forderung schreiben die meisten Teilnehmer Logeinträge auf ein dauerhaftes Medium, z.B. eine Festplatte, bevor das „Ja“ an den Koordinator gesendet wird. Nachdem alle Teilnehmer ihre Antworten an den Koordinator geliefert haben, leitet dieser die zweite und abschließende Entscheidungsphase des 2PC-Protokolls ein. Anhand der erhaltenen Antworten entscheidet der Koordinator, ob die Teilnehmer ihre lokalen Transaktionen mit einem COMMIT abschließen sollen. Im einfachsten Fall bejahen alle Teilnehmer die Möglichkeit der Durchführung, und der Koordinator weist die Systeme an, das anschließende COMMIT durchzuführen. Verneint ein Teilnehmer die Möglichkeit des COMMIT, dann muss der Koordinator entscheiden, ob die negative Antwort z.B. zu einer unerwünschten Verletzung der Atomarität der globalen Transaktion führen würde.Die komplexe Rolle des Koordinators und offene Fragen
Der Koordinator agiert als Kontrollinstanz, die überwachen kann, ob ACID-Konformität erreicht wurde. Diese Überprüfung ist nicht Teil des 2PC-Protokolls. Es ist eine zusätzliche Aufgabe, die vom Koordinator wahrgenommen werden kann. Im Zweifelsfall muss die Applikation, die den Koordinator mit der Durchführung des 2PC-Protokolls beauftragt hat, die Richtlinien vorgeben, nach denen der Koordinator über COMMIT und ROLLBACK entscheidet. Auf der Ebene der Applikation und des Koodinators ergeben sich viele Fragen: Wer überprüft die Einhaltung von zusätzlichen globalen Integritätsbedingungen und stellt die globale Konsistenz sicher? Was passiert, wenn der Koordinator ausfällt? Wie soll das System reagieren, wenn ein Teilnehmer ausfällt? Die Überprüfung von globalen Integritätsregeln und die Reaktion auf den Ausfall eines Teilnehmers ist je nach Bedarf zu implementieren. Schwieriger zu beantworten ist die Frage, was beim Ausfall des Koordinators passieren soll und wie einem Ausfall vorgebeugt werden könnte. In der vorgestellten Centralized-2PC-(C2PC-)genannten Variante des 2PC-Protokolls ist der zentrale Koordinator ein Single-Point-of-Failure. Ein Ausfall des Koordinators hinterlässt das Gesamtsystem in einen undefinierten Zustand. Vielleicht wurde deshalb in der XA-Spezifikation eine Hintertür eingebaut, die autonome Entscheidungen der Teilnehmer erlaubt. Wie später noch gezeigt wird, wurde auch ein Kommando definiert, dass die Fehlerbehandlung nach einem Neustart des Koordinators vereinfacht. Doch nicht nur die Implementierung einer Fehlerbehandlung, sondern auch Abwandlungen des C2PC-Protokolls können die Situation verbessern. Beim linearen 2PC-Protokoll ist der Koordinator nur noch Initiator. Das verteilte 2PC-Protokoll beseitigt den Single-Point-of-Failure, führt aber zu erheblich mehr Kommunikationsaufwand. Auf der Ebene des Protokolls kann jedoch erst das Drei-Phasen-Commit-Protokoll [3] das Problem lösen.Nicht die Kompexität eines Transaktionsmanagers unterschätzen
Wenn im Folgenden die seit MySQL 5.0 verfügbaren SQL-Anweisungen des 2PC-Protokolls [4] vorgestellt werden, dann sollten Sie immer im Hinterkopf behalten, dass die Anweisungen nur die Spitze des Eisbergs sind. Die Beherrschung der Kommandos ist trivial. Die Konzeption eines Transaktionsmanagers - Koordinator und Applikation - ist jedoch eine der komplexesten Aufgaben, die es bei der Implementierung einer Datenbank zu bewältigen gibt. Während sich im Enterprise-Umfeld eigenständige Transaktionsmanager finden und Java-Anwender z.B. im MySQL/J Connector von einem Teil der Komplexität abgeschirmt werden, scheint es im PHP-Umfeld keine Prototypen und Implementierungsbeispiele zu geben. Andererseits sollten Sie nicht gleich den Kopf in den Sand stecken: Überall dort, wo zwei Systeme zusammenarbeiten sollen, ermöglicht das 2PC-Protokoll neue, aufregende Möglichkeiten. Wenn Sie einfach nur zwei Systeme miteinander verbinden wollen und Sie im Fehlerfall kurzerhand einen Abbruch der globalen Transaktion durchführen, ist der Implementierungsaufwand gering und der Nutzen gewaltig. Die anschließenden Beispiele beweisen, wie einfach die SQL-Anweisungen für XA-Transaktionen sind. Es wird klar, dass eine PHP-Anwendung, die sich bewusst auf eine einfache Implementierung des Koordinators (Transaktionsmanagers) beschränkt, das Zwei-Phasen-Protokoll und die XA-Transaktionen ohne viel Aufwand nutzen kann.
XA-Transaktionen und 2PC am Beispiel
Zur Vorbereitung der Beispiele für XA-Anweisungen legen Sie eine einfache Tabelle namens xa_table mit einer einzigen Spalte ID vom Typ Integer an, die eine Speicher-Engine benutzt, welche das 2PC-Protokoll unterstützt. Unterstützt wird das Protokoll von InnoDB und NDB (MySQL-Cluster), wobei es im Falle von NDB nur intern zum Einsatz kommt.# mysql -u ulf -p ulfmysql>> CREATE TABLE xa_table(id INTEGER) ENGINE = InnoDB;
Transaction Branches - Zweige in einer globalen Transaktion
Gemäß der XA-Spezifikation ist eine globale Transaktion in voneinander unabhängige Zweige (Transaction Branches) unterteilt. Eine globale Transaktion beinhaltet einen oder mehrere Zweige. Im Artikel werden die Zweige fortan als lokale XA-Transaktion auf einem Teilnehmer (Resource Manager) bezeichnet. Auf einem Teilnehmer können gleichzeitig mehrere lokale XA-Transaktionen ablaufen, die zu einer globalen Transaktion gehören. Eine globale Transaktion wird eingeleitet durch den Koordinator (Transaction Manager), der diese auf Anforderung einer Applikation hin startet. Der Koordinator baut eine Verbindung zu den Teilnehmern auf (Resource Manager) und sendet ein xa_open() -Kommando. Der Standard macht keine genauen Vorgaben darüber, wie der Teilnehmer auf ein xa_open() zu reagieren hat, und so fasst MySQL das Kommando mit dem Verbindungsaufbau zusammen. Das Gegenstück xa_close() entspricht dem Verbindungsabbau zu MySQL.# mysql -u ulf -p ulfmysql>>
Übersicht über die XA-SQL-Anweisungen
XA {START|BEGIN} xid [JOIN|RESUME]
XA END xid [SUSPEND [FOR MIGRATE]]
XA PREPARE xid
XA COMMIT xid [ONE PHASE]
XA ROLLBACK xid
XA RECOVER
Der Branch Identifier XID und der Thread of Control
Jede lokale XA-Transaktion erhält vom Koordinator eine eindeutige XID (Branch Identifier), die zur Identifizierung der Transaktion dient. Die XID kann aus bis zu drei Elementen bestehen, die durch Kommata voneinander getrennt sind. Beim aktuellen Grade des Supports von XA durch MySQL ist nur das erste Element von semantischer Bedeutung, die anderen Elemente können ignoriert werden.mysql>> XA START 'xatest';
mysql>> INSERT INTO xa_table(id) VALUES (1);mysql>> INSERT INTO xa_table(id) VALUES (2);
mysql>> XA END 'xatest';
Einleitung der Wahlphase
Sobald Applikation und Koordinator ihre Transaktionen auf allen Teilnehmern beendet haben, beginnt die Wahlphase des 2PC-Protokoll. Der Koordinator sendet an alle Teilnehmer das Kommando xa_prepare() . xa_prepare() kann erst dann beantwortet werden, wenn alle Assoziationen von Threads mit einer Transaktion mittels xa_end() aufgehoben wurden. Dies ist in der aktuellen Implementierung immer dann der Fall, nachdem xa_end() das erste Mal für eine Transaktion aufgerufen wurde. Für Sie bedeutet dies, dass ein XA PREPARE fehlschlägt, wenn Sie noch kein XA END gesendet haben.mysql>> XA PREPARE 'xatest';
Die Entscheidungsphase
Nach Erhalt aller Antworten wird die Entscheidungsphase des 2PC-Protokoll eingeleitet. Sollten die Fluggesellschaft und die Hotelvermittlung beide signalisiert haben, dass ein Flug und ein Hotelzimmer zur Verfügung stehen, dann sendet der Koordinator das COMMIT. Statt der „normalen“ SQL-Anweisung COMMIT wird die Anweisung XA COMMIT verwendet. Wie üblich wird dem Teilnehmer durch Angabe der XID explizit mitgeteilt, welche XA-Transaktion abgeschlossen werden soll.mysql>> XA COMMIT 'xatest';
mysql>> XA ROLLBACK 'xatest';
mysql>> XA RECOVER;
Grenzen der MySQL-Implementierung
Wenn Sie mit MySQL experimentieren, werden Sie merken, dass es der MySQL-Server nicht immer „so genau nimmt“ mit dem Warten auf die Anweisung des Koordinators, um zu erfahren, was mit einer vorbereiteten Transaktion passieren soll. Das Verhalten ist jedoch dokumentiert [5]. Bricht die Verbindung des Koordinators zum Server ab oder wird der Server heruntergefahren, dann wird die vorbereitete XA-Transaktion gelöscht (ROLLBACK). Kommt es zu einem ungewollten Absturz des MySQL-Servers, bleibt die vorbereitete XA-Transaktion erhalten und der MySQL-Server wartet auf die Anweisungen des Koordinators. Es gibt noch weitere Details, in denen die XA-Spezifikation noch nicht vollständig umgesetzt wurde. So fehlen einige erweiterte Funktionen zur dynamischen Registrierung von Teilnehmern, die im XA-Standard beschrieben sind. Außerdem kann der MySQL-Server selbst nur als Teilnehmer (Resource Manager) an einer globalen Transaktion auftreten (external XA). Der Server ist nicht in der Lage, die Rolle des Koordinators zu übernehmen (internal XA). Die Gründe sind in der oben genannten Seite des Handbuchs dokumentiert. Aber mal ehrlich: Niemand braucht in der Praxis mehr als aktuell vorhanden ist! Wer will schon die Datenbank als Koordinator (Transaction Manager) einsetzen, wenn er auch eine auf die jeweilige Anwendung maßgeschneiderte Lösung selbst implementieren kann. Es ist alles implementiert, was im Rahmen von J2EE benötigt wird, und Java-Anwender setzen bereits die neuen Möglichkeiten des MySQL Connector/J ein. Es ist alles verfügbar, was PHP noch nie ausgenutzt hat. Es spricht also nichts dagegen, den Sonnenhungrigen schon bei der Reisebuchung für den Kurzurlaub mit Komfort zu verwöhnen und endlich Bundles aus Flug und Hotel anzubieten ... Ulf Wendel arbeitet bei MySQL als MaxDB Support Manager. Wenn er nicht gerade von einem Italienurlaub träumt, bemüht er sich, alte Kontakte zur deutschen PHP-Community zu pflegen, die ihm in der Vergangenheit geholfen hat, sein Geld mit der Erstellung von PHP- und MySQL-basierten Webanwendungen zu verdienen.Links & Literatur
- [1] XA-Spezifikation: www.opengroup.org/public/pubs/catalog/c193.htm
- [2] ACID: de.wikipedia.org/wiki/ACID_%28Informatik%29
- [3] Zwei- und Drei-Phasen-Commit-Protokoll: de.wikipedia.org/wiki/Commit-Protokoll#Drei-Phasen-Commit-Protokoll
- [4] XA-Anweisungen von MySQL: dev.mysql.com/doc/refman/5.0/en/xa.html
- [5] Grenzen der MySQL-Implementierung: dev.mysql.com/doc/refman/5.0/en/xa-restrictions.html




