2016-09-27 5 views
15

Aus den docs of atomic()Nicht verschachtelte Version von @atomic() in Django?

Atomblöcken Ich mag das Gegenteil

Das klingt wie ein großartiges Feature, aber in meinem Anwendungsfall verschachtelt sein: ich die Transaktion will, sobald die dauerhaft zu sein Block mit @atomic() geschmückt wird erfolgreich verlassen.

Gibt es eine Möglichkeit, die Django Transaction Handling Dauerhaftigkeit zu gewährleisten?

Hintergrund

Transaktion sind ACID. Das "D" steht für Haltbarkeit. Deshalb denke ich, dass Transaktionen nicht verschachtelt werden können, ohne das Merkmal "D" zu verlieren.

Beispiel: Wenn die innere Transaktion erfolgreich ist, die äußere Transaktion jedoch nicht, werden die äußere und die innere Transaktion zurückgesetzt. Das Ergebnis: Die innere Transaktion war nicht dauerhaft.

Ich benutze PostgreSQL, aber AFAIK sollte das nicht viel ausmachen.

Antwort

7

Sie können dies nicht über eine API tun.

Transaktionen können nicht geschachtelt werden, während alle ACID-Eigenschaften beibehalten werden und nicht alle Datenbanken verschachtelte Transaktionen unterstützen.

Nur der äußerste atomare Block erzeugt eine Transaktion. Innere atomare Blöcke erstellen einen Sicherungspunkt innerhalb der Transaktion und geben den Sicherungspunkt frei oder rückgängig, wenn der innere Block verlassen wird. Als solche liefern innere atomare Blöcke Atomizität, aber wie du angemerkt hast, z. Haltbarkeit.

Da der äußerste Atomblock eine Transaktion erzeugt, es muss Unteilbarkeit bieten, und Sie können nicht einen verschachtelten Atom-Block auf die Datenbank-Commit, wenn die Transaktion enthält, nicht verpflichtet ist.

Die einzige Möglichkeit, sicherzustellen, dass der innere Block festgeschrieben ist, besteht darin, sicherzustellen, dass der Code in der Transaktion fehlerfrei ausgeführt wird.

+0

Ich konnte die Antwort auf meine Frage in Ihrem Text nicht finden. Sie sagen, ich muss sicherstellen, dass der äußere Atomblock festgeschrieben wird. Ja das stimmt. Wie macht man das im Django? – guettli

+0

@guettli Ich habe meine Antwort aktualisiert. Dafür gibt es keine API. Die einzige Möglichkeit, dies zu erreichen, besteht darin, sicherzustellen, dass der Code in der Transaktion ohne Fehler beendet wird. – knbk

+0

@guettli, wenn Sie Low-Level-Transaktionsverwaltung in Django tun möchten, können Sie verwenden Sie das 'transaction' Paket direkt. Für ein Beispiel können Sie https://github.com/2ps/djenga/blob/master/djenga/db/nested_transactions.py sehen, was ich zurückgeschrieben habe, als django verschachtelte Transaktionen in mysql nicht unterstützt hat. – 2ps

6

Ich stimme der Antwort von knbk zu, dass es nicht möglich ist: Langlebigkeit gibt es nur auf der Ebene einer Transaktion, und Atomic bietet das. Es bietet es nicht auf der Ebene der Speicherpunkte. Je nach Anwendungsfall kann es zu einer Problemumgehung kommen.

ich Ihren Anwendungsfall zu erraten ist so etwas wie:

@atomic # possibly implicit if ATOMIC_REQUESTS is enabled 
def my_view(): 
    run_some_code() # It's fine if this gets rolled back. 
    charge_a_credit_card() # It's not OK if this gets rolled back. 
    run_some_more_code() # This shouldn't roll back the credit card. 

Ich glaube, Sie so etwas wie wollen würde:

@transaction.non_atomic_requests 
def my_view(): 
    with atomic(): 
     run_some_code() 
    with atomic(): 
     charge_a_credit_card() 
    with atomic(): 
     run_some_more_code() 

Wenn Ihr Anwendungsfall für Kreditkarten ist speziell (wie meins als ich dieses Problem vor ein paar Jahren hatte), entdeckte mein Kollege das credit card processors actually provide mechanisms for handling this. Ein ähnlicher Mechanismus könnte für Ihren Anwendungsfall arbeiten, je nach Problemstruktur:

@atomic 
def my_view(): 
    run_some_code() 
    result = charge_a_credit_card(capture=False) 
    if result.successful: 
     transaction.on_commit(lambda: result.capture()) 
    run_some_more_code() 

Eine andere Möglichkeit wäre eine Nicht-Transaktions Persistenzmechanismus benutzen für die Aufnahme, was Sie interessiert, wie eine Log-Datenbank oder eine Redis-Warteschlange für Dinge, die aufgezeichnet werden sollen.

+1

Ihr Link zu "Kreditkarten-Prozessoren bieten tatsächlich Mechanismen zur Handhabung dieser" sieht aus wie Two-Phase-Commit-Protokoll: https://en.wikipedia.org/wiki/Two-phase_commit_protocol – guettli

6

Dieser Typ von Haltbarkeit ist unmöglich wegen ACID, mit einer Verbindung. (d. h. dass ein verschachtelter Block festgeschrieben wird, während der äußere Block zurückgesetzt wird). Dies ist eine Konsequenz von ACID, kein Problem von Django. Stellen Sie sich eine Super-Datenbank vor und der Fall, dass die Tabelle B einen Fremdschlüssel für die Tabelle A enthält.

Wenn die innere "Transaktion" dauerhaft sein sollte, während die (äußere) Transaktion zurückgesetzt wird, dann wäre die Integrität gebrochen. Die Rollback-Operation muss immer so implementiert werden, dass sie niemals fehlschlagen kann. Daher würde keine Datenbank eine verschachtelte unabhängige Transaktion implementieren. Es wäre gegen den Grundsatz der Kausalität und die Integrität kann nach einem solchen selektiven Rollback nicht garantiert werden. Es ist auch gegen Atomizität.

Die Transaktion bezieht sich auf eine Datenbankverbindung. Wenn Sie zwei Verbindungen erstellen, werden zwei unabhängige Transaktionen erstellt. Eine Verbindung erkennt nicht festgeschriebene Zeilen anderer Transaktionen (es ist möglich, diese Isolationsstufe festzulegen, aber es hängt vom Datenbank-Back-End ab) und keine Fremdschlüssel können erstellt werden und die Integrität wird nach dem Rollback durch die Datenbank beibehalten Backend-Design.

Django unterstützt mehrere Datenbanken, daher mehrere Verbindungen.

# no ATOMIC_REQUESTS should be set for "other_db" in DATABASES 

@transaction.atomic # atomic for the database "default" 
def my_view(): 
    with atomic(): # or set atomic() here, for the database "default" 
     some_code() 
     with atomic("other_db"): 
      row = OtherModel.objects.using("other_db").create(**kwargs) 
     raise DatabaseError 

Die Daten in "other_db" bleibt festgelegt.

Es ist wahrscheinlich in Django möglich, einen Trick mit zwei Verbindungen zur gleichen Datenbank zu erstellen, wie es zwei Datenbanken mit einigen Datenbank-Backends wären, aber ich bin sicher, dass es ungetestet ist, es würde zu Fehlern neigen, Bei Problemen mit Migrationen ist eine größere Belastung durch das Datenbank-Backend erforderlich, das bei jeder Anforderung echte parallele Transaktionen erstellen muss und nicht optimiert werden kann. Es ist besser, zwei echte Datenbanken zu verwenden oder den Code zu reorganisieren.

Die Einstellung DATABASE_ROUTERS ist sehr nützlich, aber ich bin mir noch nicht sicher, ob Sie an mehreren Verbindungen interessiert sind.