30 Stimmen

Wie kann man feststellen, dass eine Transaktion bereits begonnen wurde?

Ich verwende Zend_Db, um einige Daten innerhalb einer Transaktion einzufügen. Meine Funktion startet eine Transaktion und ruft dann eine andere Methode auf, die ebenfalls versucht, eine Transaktion zu starten und natürlich fehlschlägt (ich verwende MySQL5). Die Frage ist also - wie kann ich erkennen, dass die Transaktion bereits gestartet wurde? Hier ist ein Beispiel für Code:

       try {
                    Zend_Registry::get('database')->beginTransaction();

                    $totals = self::calculateTotals($Cart);
                    $PaymentInstrument = new PaymentInstrument;
                    $PaymentInstrument->create();
                    $PaymentInstrument->validate();
                    $PaymentInstrument->save();

                    Zend_Registry::get('database')->commit();
                    return true;

            } catch(Zend_Exception $e) {
                    Bootstrap::$Log->err($e->getMessage());
                    Zend_Registry::get('database')->rollBack();
                    return false;
            }

Innerhalb von PaymentInstrument::create gibt es eine weitere beginTransaction-Anweisung, die die Ausnahme erzeugt, die besagt, dass die Transaktion bereits gestartet wurde.

30voto

Bill Karwin Punkte 493880

Der Rahmen hat keine Möglichkeit zu wissen, ob Sie eine Transaktion gestartet haben. Sie können sogar $db->query('START TRANSACTION') die das Framework nicht kennt, weil es die von Ihnen ausgeführten SQL-Anweisungen nicht analysiert.

Der Punkt ist, dass es in der Verantwortung der Anwendung liegt, zu verfolgen, ob Sie eine Transaktion begonnen haben oder nicht. Das kann das Framework nicht leisten.

Ich weiß, dass einige Frameworks das versuchen und so verrückte Dinge tun, wie z.B. zählen, wie oft man eine Transaktion begonnen hat, und sie erst auflösen, wenn man eine entsprechende Anzahl von Commits oder Rollbacks durchgeführt hat. Aber das ist totaler Blödsinn, weil keine Ihrer Funktionen wissen kann, ob Commit oder Rollback es tatsächlich tun, oder ob sie in einer anderen Schicht der Verschachtelung sind.

(Sieht man, dass ich diese Diskussion schon ein paar Mal geführt habe? :-)

Aktualisierung 1: Vorantreiben ist eine PHP-Datenbank-Zugriffsbibliothek, die das Konzept der "inneren Transaktion" unterstützt, die sich nicht festlegt, wenn Sie sie dazu auffordern. Durch den Beginn einer Transaktion wird lediglich ein Zähler erhöht, und durch die Übergabe/Rückgabe wird der Zähler verringert. Im Folgenden finden Sie einen Auszug aus einem Mailinglisten-Thread, in dem ich einige Szenarien beschreibe, in denen dies nicht funktioniert.

Update 2: Doktrin DBAL verfügt ebenfalls über diese Funktion. Sie nennen es Transaktionsverschachtelung.


Ob es Ihnen gefällt oder nicht, Transaktionen sind "global" und gehorchen nicht der objektorientierten Kapselung.

Problemszenario Nr. 1

Ich rufe commit() Sind meine Änderungen verbindlich? Wenn ich mich in einer "inneren Transaktion" befinde, sind sie es nicht. Der Code, der die äußere Transaktion verwaltet, könnte einen Rollback durchführen, und meine Änderungen würden ohne mein Wissen oder meine Kontrolle verworfen werden.

Zum Beispiel:

  1. Modell A: Beginn der Transaktion
  2. Modell A: einige Änderungen vornehmen
  3. Modell B: Beginn der Transaktion (silent no-op)
  4. Modell B: einige Änderungen vornehmen
  5. Modell B: Commit (stiller No-op)
  6. Modell A: Rollback (verwirft sowohl die Änderungen von Modell A als auch die von Modell B)
  7. Modell B: WTF!? Was ist mit meinen Änderungen passiert?

Problemszenario #2

Wenn eine innere Transaktion einen Rollback durchführt, könnte sie legitime Änderungen einer äußeren Transaktion verwerfen. Wenn die Kontrolle an den äußeren Code zurückgegeben wird, geht dieser davon aus, dass seine Transaktion noch aktiv ist und committed werden kann. Mit Ihrem Patch könnten sie commit() und da die transDepth jetzt 0 ist, würde es stillschweigend die $transDepth auf -1 setzen und true zurückgeben, nachdem sie nichts begangen haben.

Problemszenario #3

Wenn ich anrufe commit() o rollback() wenn keine Transaktion aktiv ist, setzt es die $transDepth auf -1. Die nächste beginTransaction() erhöht die Stufe auf 0, was bedeutet, dass die Transaktion weder rückgängig gemacht noch festgeschrieben werden kann. Nachfolgende Aufrufe von commit() wird die Transaktion einfach auf -1 oder weiter dekrementiert, und Sie werden nie in der Lage sein, eine Zusage zu machen, bis Sie eine weitere überflüssige beginTransaction() um die Stufe wieder zu erhöhen.

Der Versuch, Transaktionen in der Anwendungslogik zu verwalten, ohne der Datenbank die Buchführung zu überlassen, ist im Grunde genommen eine zum Scheitern verurteilte Idee. Wenn zwei Modelle in einer Anwendungsanforderung eine explizite Transaktionssteuerung verwenden sollen, müssen Sie zwei DB-Verbindungen öffnen, eine für jedes Modell. Dann kann jedes Modell seine eigene aktive Transaktion haben, die unabhängig voneinander bestätigt oder zurückgenommen werden kann.

4voto

Führen Sie ein try/catch durch: Wenn die Ausnahme ist, dass eine Transaktion bereits begonnen hat (basierend auf dem Fehlercode oder der Nachricht der Zeichenfolge, was auch immer), machen Sie weiter. Andernfalls werfen Sie die Ausnahme erneut.

2voto

Imran Punkte 81131

Den Rückgabewert von beginTransaction() in Zend_Registry speichern, und später überprüfen.

2voto

Sean McSomething Punkte 6270

Wenn ich mir die Zend_Db sowie die Adapter (sowohl mysqli als auch PDO Versionen) ansehe, sehe ich nicht wirklich eine schöne Möglichkeit, den Transaktionsstatus zu überprüfen. Es scheint eine ZF-Ausgabe Diesbezüglich gibt es glücklicherweise einen Patch, der bald erscheinen soll.

Wenn Sie vorerst keinen inoffiziellen ZF-Code verwenden möchten, können Sie den mysqli-Dokumentation sagt, Sie können SELECT @@autocommit um herauszufinden, ob Sie sich gerade in einer Transaktion befinden (ähm... nicht im Autocommit-Modus).

2voto

curlyhairedgenius Punkte 794

Für innoDB sollten Sie in der Lage sein

SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX WHERE TRX_MYSQL_THREAD_ID = CONNECTION_ID();

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X