2016-05-21 3 views
1

Ich habe durch Ressourcen für rollBack(), commit() und various transaction stuff gesucht, aber ich kann nicht finden, ob rollBack() aufgerufen werden kann, nachdem commit() bereits aufgerufen wurde.PHP, MySQL, PDO-Transaktion - Kann rollBack() verwendet werden, nachdem commit() aufgerufen wurde?

Die Situation ist folgende:

Ich habe zwei verschiedene Datenbanken: $dbu = new PDO(..db1..) und $dbb = new PDO(..db2..)

Beide Datenbanken haben Tabellen, die innerhalb einer einzigen Funktion aktualisiert werden. Die Operation ist alle oder keine - entweder alle Tabellen wurden erfolgreich aktualisiert, oder keine sind.

Verwendung von zwei separaten Transaktionen, wenn die Transaktion für $dbu erfolgreich abgeschlossen, aber die Transaktion für $dbb ausfällt, muß ich ungeschehen machen, was in der ersten Transaktion durchgeführt wurde:

Codeblock 1

$dbu->beginTransaction(); 
try{ 
    $stmt = $dbu->prepare(...); 
    $stmt->execute(); 

    // stuff 

    $dbu->commit(); 
}catch(Exception $e){ 
    // do stuff 

    $dbu->rollBack(); 
    exit(); 
} 

$dbb->beginTransaction(); 
try{ 
    $stmt = $dbb->prepare(...); 
    $stmt->execute(); 

    // stuff 

    $dbb->commit(); 
}catch(Exception $e){ 
    // do stuff 

    $dbb->rollBack(); 

    // Need to undo what we did 
    $dbu->beginTransaction(); 
    try{ 
     $stmt = $dbu->prepare(...); 
     $stmt->execute(); 

     // opposite of whatever operation was in the first transaction 

     $dbu->commit(); 
    }catch(Exception $e){ 
    } 

    exit(); 
} 

Dies ist unordentlich und unzuverlässig, wenn etwas mit der Verbindung zwischen den beiden primären Transaktionen passiert.

Also was ich stattdessen tun möchte, ist die zweite Transaktion innerhalb der ersten verschachteln. Es scheint logisch, dass ich dies tun könnte, denn $dbu und $dbb sind zwei eindeutige PDO-Objekte, die auf zwei separate Datenbanken verweisen. Es sieht aus wie:

Codeblock 2

$dbu->beginTransaction(); 
try{ 
    $stmt = $dbu->prepare(...); 
    $stmt->execute(); 

    // stuff 

    $dbb->beginTransaction(); 
    try{ 
     $stmt = $dbb->prepare(...); 
     $stmt->execute(); 

     // stuff 

     $dbb->commit(); 
    }catch(Exception $e){ 
     // do stuff 

     $dbb->rollBack(); 
     $dbu->rollBack(); // Since $dbu was first part of transaction, it needs to be rolled back too 
     exit(); 
    } 

    $dbu->commit(); 
}catch(Exception $e){ 
    // do stuff 

    $dbu->rollBack(); 
    $dbb->rollBack(); // **THIS IS THE TRICKY LINE!** 
    exit(); 
} 

Seit commit() für $dbunach die gesamte $dbb Transaktion aufgerufen wird, kann der Fall eintreten, wo $dbb erfolgreich war, und $dbu gescheitert. Wenn das passiert, muss ich rückgängig machen, was in der $dbb Transaktion getan wurde.

So ...

Kann ich nennen $dbb->rollBack(); (nahe dem Ende des Codeblock 2) NACH$dbb->commit(); gelaufen? Oder stehe ich in der gleichen Situation, in der ich ursprünglich war, wo ich manuell rückgängig machen muss, was auch immer in der $dbb Transaktion passiert ist? Auch dies ist nicht ideal. Wenn die Verbindung in der Mitte davon fällt, könnte ich mit Daten in den $dbb Tabellen verlassen werden, die nicht dort sein sollten (weil die $dbu Transaktion fehlgeschlagen ist).

Vielleicht kann ich die beiden Transaktionen in einen einzigen try/catch Block kombinieren?

Codeblock 3

$dbu->beginTransaction(); 
$dbb->beginTransaction(); 

try{ 
    $stmt = $dbu->prepare(...); 
    $stmt->execute(); 

    $stmt2 = $dbb->prepare(...); 
    $stmt2->execute(); 

    // stuff 

    $dbu->commit(); 
    $dbb->commit(); 
}catch(Exception $e){ 
    // do stuff 

    $dbu->rollBack(); 
    $dbb->rollBack(); // **THIS IS THE TRICKY LINE!** 
    exit(); 
} 

Aber das sieht nicht viel anders aus als -Code Block 2, weil wir immer noch die Situation haben können, wo $dbu->commit(); erfolgreich ist, aber $dbb->commit(); ausfällt.Wenn das passiert, versuchen wir immer noch, $dbu->rollBack(); aufzurufen, nachdem sein Partner-Commit bereits verarbeitet wurde.

Wenn ich kann nicht Anruf rollBack() nach commit(), gibt es eine häufig verwendete Methode, um dieses 2-DB Problem zu lösen? Etwas, das so effizient ist wie rollBack() und keine vollständige zusätzliche Transaktion erfordert, um die vorherige Operation rückgängig zu machen.

EDIT 1

Hinzufügen auf Codeblock 3, konnte ich jede Ausführung überprüfen, wie sie genannt werden?

Codeblock 4

$dbu->beginTransaction(); 
$dbb->beginTransaction(); 

try{ 
    $stmt = $dbu->prepare(...); 
    if(!$stmt->execute()){ 
     throw new Exeption('something somethign'); 
    } 

    $stmt2 = $dbb->prepare(...); 
    if(!$stmt2->execute()){ 
     throw new Exeption('something two'); 
    } 

    // stuff 

    $dbu->commit(); 
    $dbb->commit(); 
}catch(PDOException $e){ 
    // do stuff 

    $dbu->rollBack(); 
    $dbb->rollBack(); // **THIS IS THE TRICKY LINE!** 
    exit(); 
}catch(Exception $e){ 
    // do stuff 

    $dbu->rollBack(); 
    $dbb->rollBack(); // **THIS IS THE TRICKY LINE!** 
    exit(); 
} 

Wird dies die beiden commit Aussagen dazu beitragen sicherzustellen, haben die bestmögliche Chance auf Erfolg? Oder wirft der try/catch Block automatisch PDOException, bevor die benutzerdefinierten jemals aufgerufen werden? Es wäre schön, einen einfachen Bezeichner zu haben, um zu wissen, welche Transaktion fehlschlägt, im Gegensatz zu der gesamten $e->getMessage();.

Antwort

2

Sie können keine festgeschriebenen Änderungen rückgängig machen.

Wie bei Ihrem other question Code Block 3 ist der Weg zu gehen. Auch wenn ein Commit fehlschlägt, wird es nicht aufgrund von häufigen Fehlern (wie falscher Syntax- oder Constraint-Verletzung oder was sonst) fehlschlagen. Hypothetisch könnte der gesamte PHP-Prozess genau zwischen den beiden Commits abgebrochen werden, wobei letzteres zurückgesetzt wird und Sie keine Chance haben, die resultierenden Fehler im Code zu beheben. Allerdings müssen Sie diese seltenen Ausnahmen separat behandeln (z. B. Sicherungen), weil ich keine effiziente Möglichkeit sehe, sie in Code zu verarbeiten.

Denken Sie auch daran, dass beim Commit die Änderungen bereits angewendet, aber nicht "veröffentlicht" wurden. Daher ist das Commit selbst selten (nur aus außergewöhnlichen Gründen).

@EDIT 1

Die Art und Weise Sie Fehler behandeln hängt davon ab, wie Sie Ihre PDO Instanzen einrichten. Siehe documentation on how errors can be handled by PDO.

Ihr Codeblock 4 funktioniert, wenn Sie den Standardmodus verwenden (den Fehlermodus nicht explizit einstellen oder auf PDO::ERRMODE_SILENT setzen).

+0

Ok, vergessen, das Gefühl mit der macht „verpflichten sich selten zum Scheitern verurteilt“, da alle die Beinarbeit getan wurde, zu diesem Zeitpunkt. Ich habe am Ende der Frage eine Bearbeitung hinzugefügt, mit ein paar Gedanken, um Probleme zu mildern, wenn Sie mir Ihre Gedanken mitteilen könnten, wenn es sich sogar lohnt hinzuzufügen. – Birrel

+0

Ah, ich verstehe. Ja, ich habe es auf 'setAttribute (PDO :: ATTR_ERRMODE, PDO :: ERRMODE_EXCEPTION);' eingestellt, also denke ich, dass es meine benutzerdefinierten Ausnahmen nicht sehen wird. – Birrel

+1

Wenn Sie den Fehlermodus so einstellen, dass Ausnahmen ausgelöst werden, müssen Sie jede 'prepare' /' execute' Anweisung mit ihrem eigenen 'try' /' catch' Block umgeben, um zwischen ihnen zu unterscheiden - was Sie wahrscheinlich schon erraten haben. –

3

Es ist unmöglich, eine ordnungsgemäße Transaktion über verschiedene Datenbankverbindungen hinweg durchzuführen.

Obwohl Sie einige peinliche Problemumgehungen machen können, wird es Ihre Transaktion nicht wirklich haben.

Also müssen Sie entweder alle Operationen innerhalb einer einzigen Datenbank halten oder über Transaktionen

+0

Was ich denke, dass ich tun werde, ist dasselbe wie * Codeblock 3/4 * und ein Skript wird regelmäßig in einem Cron-Job ausgeführt (sogar einmal am Tag ist mehr als genug), der alle Tabellen vergleicht, und Entfernt Miss-Matches aufgrund von Bad. Habe gerade ein paar Tage damit verbracht, mehr als 1000 Abfragen auf gruppierte Transaktionen wie die oben genannten umzustellen, also wird es für ein bisschen so bleiben. Vielleicht müssen wir das Schema und die Layouts in Zukunft ändern oder uns für eine andere Datenbank entscheiden. – Birrel

+0

@Birrel Wenn du an einen anderen Weg denkst, solltest du definitiv den CAP-Theorem (https://en.wikipedia.org/wiki/CAP_theorem) betrachten, da ich denke, dass dies mit dem verbunden ist, auf das dein Common Sense hinweisen wollte aus. –

Verwandte Themen