Sessions / Queries

Ich lade Daten mit meiner Session neu, aber sie sieht keine Änderungen, die ich woanders committet habe

Das Hauptproblem bezüglich dieses Verhaltens ist, dass die Session so agiert, als ob die Transaktion den seriellen Isolationszustand hätte, auch wenn dies nicht der Fall ist (und meistens ist es das nicht). Praktisch bedeutet dies, dass die Session keine Daten ändert, die sie bereits innerhalb des Gültigkeitsbereichs einer Transaktion gelesen hat.

Wenn Ihnen der Begriff „Isolationsstufe“ unbekannt ist, müssen Sie zuerst diesen Link lesen

Isolationsstufe

Kurz gesagt bedeutet die serielle Isolationsstufe im Allgemeinen, dass Sie, sobald Sie eine Reihe von Zeilen in einer Transaktion SELECTieren, jedes Mal, wenn Sie diese SELECT-Anweisung erneut ausführen, *dieselbe Daten* zurückerhalten. Wenn Sie sich in der nächstniedrigeren Isolationsstufe, „repeatable read“, befinden, sehen Sie neu hinzugefügte Zeilen (und keine gelöschten Zeilen mehr), aber für Zeilen, die Sie *bereits* geladen haben, sehen Sie keine Änderung. Nur wenn Sie sich in einer niedrigeren Isolationsstufe, z. B. „read committed“, befinden, ist es möglich, dass der Wert einer Datenzeile geändert wird.

Informationen zur Steuerung der Isolationsstufe bei Verwendung des SQLAlchemy ORM finden Sie unter Festlegen von Transaktionsisolationsstufen / DBAPI AUTOCOMMIT.

Um die Dinge dramatisch zu vereinfachen, arbeitet die Session selbst im Rahmen einer vollständig isolierten Transaktion und überschreibt keine zugeordneten Attribute, die sie bereits gelesen hat, es sei denn, Sie weisen sie dazu an. Der Anwendungsfall, Daten neu lesen zu wollen, die Sie in einer laufenden Transaktion bereits geladen haben, ist ein *ungewöhnlicher* Anwendungsfall, der in vielen Fällen keine Auswirkungen hat, daher wird dies als Ausnahme und nicht als Norm betrachtet; um innerhalb dieser Ausnahme zu arbeiten, werden mehrere Methoden bereitgestellt, um das Laden spezifischer Daten innerhalb des Kontexts einer laufenden Transaktion zu ermöglichen.

Um zu verstehen, was wir unter „die Transaktion“ verstehen, wenn wir über die Session sprechen, ist Ihre Session dazu gedacht, nur innerhalb einer Transaktion zu arbeiten. Eine Übersicht hierüber finden Sie unter Verwalten von Transaktionen.

Sobald wir herausgefunden haben, wie unsere Isolationsstufe ist, und wir glauben, dass unsere Isolationsstufe niedrig genug eingestellt ist, sodass wir neue Daten in unserer Session sehen sollten, wenn wir eine Zeile erneut SELECTieren, wie sehen wir sie dann?

Drei Wege, vom häufigsten zum seltensten

  1. Wir beenden einfach unsere Transaktion und starten eine neue bei nächstem Zugriff mit unserer Session, indem wir Session.commit() aufrufen (beachten Sie, dass bei der weniger gebräuchlichen „Autocommit“-Funktion der Session auch ein Aufruf von Session.begin() stattfindet). Die überwiegende Mehrheit der Anwendungen und Anwendungsfälle hat keine Probleme damit, Daten in anderen Transaktionen nicht „sehen“ zu können, da sie diesem Muster folgen, das den Kern der Best Practice von **kurzlebigen Transaktionen** bildet. Siehe Wann erstelle ich eine Session, wann committe ich sie und wann schließe ich sie? für einige Gedanken dazu.

  2. Wir weisen unsere Session an, bereits gelesene Zeilen neu zu lesen, entweder wenn wir sie das nächste Mal abfragen, mit Session.expire_all() oder Session.expire(), oder sofort bei einem Objekt mit refresh. Details hierzu finden Sie unter Aktualisieren / Verwerfen.

  3. Wir können ganze Abfragen ausführen und dabei festlegen, dass sie bereits geladene Objekte beim Lesen von Zeilen definitiv überschreiben, indem wir „populate existing“ verwenden. Dies ist eine Ausführungsoption, die unter Populate Existing beschrieben wird.

Aber denken Sie daran: **Das ORM kann keine Änderungen an Zeilen sehen, wenn unsere Isolationsstufe „repeatable read“ oder höher ist, es sei denn, wir starten eine neue Transaktion**.

„Die Transaktion dieser Session wurde aufgrund einer vorherigen Ausnahme während des Flush zurückgerollt.“ (oder ähnlich)

Dies ist ein Fehler, der auftritt, wenn eine Session.flush() eine Ausnahme auslöst, die Transaktion zurückrollt, aber weitere Befehle auf der Session ohne expliziten Aufruf von Session.rollback() oder Session.close() aufgerufen werden.

Dies entspricht normalerweise einer Anwendung, die bei Session.flush() oder Session.commit() eine Ausnahme abfängt und die Ausnahme nicht richtig behandelt. Zum Beispiel

from sqlalchemy import create_engine, Column, Integer
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base(create_engine("sqlite://"))


class Foo(Base):
    __tablename__ = "foo"
    id = Column(Integer, primary_key=True)


Base.metadata.create_all()

session = sessionmaker()()

# constraint violation
session.add_all([Foo(id=1), Foo(id=1)])

try:
    session.commit()
except:
    # ignore error
    pass

# continue using session without rolling back
session.commit()

Die Verwendung der Session sollte in eine Struktur passen, die diesem ähnelt

try:
    # <use session>
    session.commit()
except:
    session.rollback()
    raise
finally:
    session.close()  # optional, depends on use case

Viele Dinge können außer Flushes zu einem Fehler innerhalb von try/except führen. Anwendungen sollten sicherstellen, dass ein System von „Framing“ auf ORM-orientierte Prozesse angewendet wird, damit Verbindungs- und Transaktionsressourcen eine definitive Grenze haben und Transaktionen bei Auftreten von Fehlern explizit zurückgerollt werden können.

Dies bedeutet nicht, dass es überall in einer Anwendung try/except-Blöcke geben sollte, was keine skalierbare Architektur wäre. Stattdessen ist ein typischer Ansatz, dass beim ersten Aufruf ORM-orientierter Methoden und Funktionen der Prozess, der die Funktionen von ganz oben aufruft, sich in einem Block befindet, der Transaktionen bei erfolgreichem Abschluss einer Reihe von Operationen committet und Transaktionen zurückrollt, wenn Operationen aus irgendeinem Grund fehlschlagen, einschließlich fehlerhafter Flushes. Es gibt auch Ansätze, die Funktionsdekorationen oder Kontextmanager verwenden, um ähnliche Ergebnisse zu erzielen. Die Art des gewählten Ansatzes hängt stark von der Art der zu schreibenden Anwendung ab.

Für eine detaillierte Diskussion darüber, wie die Verwendung der Session organisiert werden kann, siehe Wann erstelle ich eine Session, wann committe ich sie und wann schließe ich sie?.

Aber warum besteht flush() darauf, ein ROLLBACK auszugeben?

Es wäre großartig, wenn Session.flush() teilweise abgeschlossen werden könnte und dann nicht zurückgerollt würde. Dies liegt jedoch außerhalb seiner aktuellen Fähigkeiten, da seine interne Buchführung modifiziert werden müsste, so dass es jederzeit angehalten und exakt mit dem, was auf die Datenbank geschrieben wurde, konsistent sein kann. Obwohl dies theoretisch möglich ist, wird die Nützlichkeit der Verbesserung stark dadurch beeinträchtigt, dass viele Datenbankoperationen ohnehin ein ROLLBACK erfordern. Insbesondere PostgreSQL hat Operationen, die, sobald sie fehlschlagen, die Transaktion nicht fortsetzen können.

test=> create table foo(id integer primary key);
NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "foo_pkey" for table "foo"
CREATE TABLE
test=> begin;
BEGIN
test=> insert into foo values(1);
INSERT 0 1
test=> commit;
COMMIT
test=> begin;
BEGIN
test=> insert into foo values(1);
ERROR:  duplicate key value violates unique constraint "foo_pkey"
test=> insert into foo values(2);
ERROR:  current transaction is aborted, commands ignored until end of transaction block

Was SQLAlchemy bietet, um beide Probleme zu lösen, ist die Unterstützung von SAVEPOINT über Session.begin_nested(). Mit Session.begin_nested() können Sie eine potenziell fehlschlagende Operation in einer Transaktion einrahmen und dann zum Punkt vor ihrem Versagen „zurückrollen“, während die umschließende Transaktion beibehalten wird.

Aber warum reicht der eine automatische Aufruf von ROLLBACK nicht aus? Warum muss ich erneut ROLLBACK durchführen?

Das durch flush() verursachte Rollback ist nicht das Ende des vollständigen Transaktionsblocks; während es die Datenbanktransaktion beendet, gibt es aus Sicht der Session immer noch eine Transaktion, die sich nun in einem inaktiven Zustand befindet.

Gegeben sei ein Block wie dieser

sess = Session()  # begins a logical transaction
try:
    sess.flush()

    sess.commit()
except:
    sess.rollback()

Wenn oben eine Session zum ersten Mal erstellt wird, unter der Annahme, dass „Autocommit-Modus“ nicht verwendet wird, wird innerhalb der Session eine logische Transaktion etabliert. Diese Transaktion ist „logisch“, da sie erst dann Datenbankressourcen nutzt, wenn eine SQL-Anweisung aufgerufen wird, woraufhin eine Transaktion auf Connection- und DBAPI-Ebene gestartet wird. Unabhängig davon, ob Transaktionen auf Datenbankebene Teil ihres Zustands sind oder nicht, bleibt die logische Transaktion bestehen, bis sie mit Session.commit(), Session.rollback() oder Session.close() beendet wird.

Wenn der obige flush() fehlschlägt, befindet sich der Code immer noch innerhalb der Transaktion, die durch den try/commit/except/rollback-Block gerahmt wird. Wenn flush() die logische Transaktion vollständig zurückrollen würde, würde dies bedeuten, dass die Session im except-Block in einem sauberen Zustand wäre, bereit, neue SQLs für eine völlig neue Transaktion auszugeben, und der Aufruf von Session.rollback() wäre nicht mehr korrekt. Insbesondere hätte die Session zu diesem Zeitpunkt eine neue Transaktion begonnen, auf die sich das Session.rollback() fälschlicherweise beziehen würde. Anstatt SQL-Operationen auf einer neuen Transaktion fortzusetzen, an der Stelle, an der der normale Gebrauch ein Rollback vorschreibt, weigert sich die Session, fortzufahren, bis das explizite Rollback tatsächlich stattfindet.

Mit anderen Worten, es wird erwartet, dass der aufrufende Code **immer** Session.commit(), Session.rollback() oder Session.close() aufruft, um den aktuellen Transaktionsblock zu verarbeiten. flush() hält die Session innerhalb dieses Transaktionsblocks, damit das Verhalten des obigen Codes vorhersehbar und konsistent ist.

Wie erstelle ich eine Abfrage, die jeder Abfrage immer einen bestimmten Filter hinzufügt?

Siehe das Rezept unter FilteredQuery.

Meine Abfrage gibt nicht die gleiche Anzahl von Objekten zurück, wie query.count() mir sagt – warum?

Das Query-Objekt dedupliziert die Objekte anhand des Primärschlüssels, wenn es angewiesen wird, eine Liste von ORM-zugeordneten Objekten zurückzugeben. Das heißt, wenn wir zum Beispiel das unter Verwenden von ORM-Deklarativen Formularen zur Definition von Tabellenmetadaten beschriebene User-Mapping verwenden und eine SQL-Abfrage wie die folgende hätten

q = session.query(User).outerjoin(User.addresses).filter(User.name == "jack")

Die im Tutorial verwendeten Beispieldaten enthalten zwei Zeilen in der addresses-Tabelle für die users-Zeile mit dem Namen 'jack', Primärschlüsselwert 5. Wenn wir für die obige Abfrage Query.count() aufrufen, erhalten wir die Antwort **2**

>>> q.count()
2

Wenn wir jedoch Query.all() ausführen oder über die Abfrage iterieren, erhalten wir **ein Element** zurück

>>> q.all()
[User(id=5, name='jack', ...)]

Dies liegt daran, dass, wenn das Query-Objekt vollständige Entitäten zurückgibt, diese **dedupliziert** werden. Dies geschieht nicht, wenn wir stattdessen einzelne Spalten anfordern

>>> session.query(User.id, User.name).outerjoin(User.addresses).filter(
...     User.name == "jack"
... ).all()
[(5, 'jack'), (5, 'jack')]

Es gibt zwei Hauptgründe für die Deduplizierung durch die Query

  • Damit das joined eager loading korrekt funktioniertJoined Eager Loading funktioniert, indem Zeilen mithilfe von Joins gegen verknüpfte Tabellen abgefragt werden, wobei Zeilen aus diesen Joins dann in Sammlungen auf den führenden Objekten geroutet werden. Um dies zu tun, müssen Zeilen abgerufen werden, bei denen der Primärschlüssel des führenden Objekts für jeden Untereintrag wiederholt wird. Dieses Muster kann sich dann in weitere Unter-Sammlungen fortsetzen, sodass für ein einzelnes führendes Objekt ein Vielfaches von Zeilen verarbeitet werden kann, z. B. User(id=5). Die Deduplizierung ermöglicht es uns, Objekte so zu erhalten, wie sie abgefragt wurden, z. B. alle User()-Objekte, deren Name 'jack' ist, was für uns ein Objekt ist, mit der User.addresses-Sammlung, die entweder durch lazy='joined' auf dem relationship() oder über die joinedload()-Option angegeben wurde, eager geladen wurde. Aus Konsistenzgründen wird die Deduplizierung trotzdem angewendet, unabhängig davon, ob joinedload eingerichtet ist, da die Kernphilosophie des eager loading darin besteht, dass diese Optionen niemals die Ergebnisse beeinflussen.

  • Um Verwirrung bezüglich der Identitäts-Map zu vermeiden – dies ist zugegebenermaßen der weniger kritische Grund. Da die Session eine Identitäts-Map verwendet, gibt es, obwohl unser SQL-Ergebnis-Set zwei Zeilen mit dem Primärschlüssel 5 hat, nur ein User(id=5)-Objekt innerhalb der Session, das eindeutig anhand seiner Identität, d. h. der Kombination aus Primärschlüssel/Klasse, beibehalten werden muss. Es ergibt nicht wirklich viel Sinn, wenn man nach User()-Objekten abfragt, dass man dasselbe Objekt mehrmals in der Liste erhält. Eine geordnete Menge wäre möglicherweise eine bessere Darstellung dessen, was Query zurückgeben möchte, wenn es vollständige Objekte zurückgibt.

Das Problem der Deduplizierung von Query bleibt problematisch, hauptsächlich aus dem einzigen Grund, dass die Methode Query.count() inkonsistent ist, und der aktuelle Status ist, dass joined eager loading in früheren Versionen zuerst durch die Strategie „subquery eager loading“ und neuerdings durch die Strategie „select IN eager loading“ abgelöst wurde, die beide generell besser für die Sammlung von eager loading geeignet sind. Da diese Entwicklung fortschreitet, kann SQLAlchemy dieses Verhalten bei Query ändern, was auch neue APIs beinhalten kann, um dieses Verhalten direkter zu steuern, und auch das Verhalten von joined eager loading ändern kann, um ein konsistenteres Nutzungsmuster zu schaffen.

Ich habe ein Mapping gegen einen Outer Join erstellt, und obwohl die Abfrage Zeilen zurückgibt, werden keine Objekte zurückgegeben. Warum nicht?

Zeilen, die von einem Outer Join zurückgegeben werden, können für einen Teil des Primärschlüssels NULL enthalten, da der Primärschlüssel eine Kombination aus beiden Tabellen ist. Das Query-Objekt ignoriert eingehende Zeilen, die keinen akzeptablen Primärschlüssel haben. Basierend auf der Einstellung des Flags allow_partial_pks auf Mapper wird ein Primärschlüssel akzeptiert, wenn der Wert mindestens einen Nicht-NULL-Wert aufweist, oder alternativ, wenn der Wert keine NULL-Werte hat. Siehe allow_partial_pks bei Mapper.

Ich verwende joinedload() oder lazy=False, um einen JOIN/OUTER JOIN zu erstellen, und SQLAlchemy konstruiert die falsche Abfrage, wenn ich versuche, ein WHERE, ORDER BY, LIMIT usw. hinzuzufügen (was sich auf den (OUTER) JOIN stützt)

Die von joined eager loading generierten Joins werden nur verwendet, um verwandte Sammlungen vollständig zu laden, und sind so konzipiert, dass sie keine Auswirkungen auf die primären Ergebnisse der Abfrage haben. Da sie anonym mit Aliassen versehen sind, können sie nicht direkt referenziert werden.

Details zu diesem Verhalten finden Sie unter Der Zen des Joined Eager Loading.

Query hat keine __len__(), warum nicht?

Die Python-Magic-Methode __len__(), die auf ein Objekt angewendet wird, ermöglicht die Verwendung der integrierten Funktion len() zur Bestimmung der Länge der Sammlung. Es ist naheliegend, dass ein SQL-Abfrageobjekt __len__() mit der Methode Query.count() verknüpft, die ein SELECT COUNT ausgibt. Der Grund, warum dies nicht möglich ist, liegt darin, dass die Auswertung der Abfrage als Liste zwei SQL-Aufrufe anstelle von einem verursachen würde

class Iterates:
    def __len__(self):
        print("LEN!")
        return 5

    def __iter__(self):
        print("ITER!")
        return iter([1, 2, 3, 4, 5])


list(Iterates())

Ausgabe

ITER!
LEN!

Wie verwende ich Text-SQL mit ORM-Abfragen?

Siehe

Ich rufe Session.delete(myobject) auf, und es wird nicht aus der übergeordneten Sammlung entfernt!

Siehe Hinweise zum Löschen – Objekte löschen, die aus Sammlungen und Skalarbeziehungen referenziert werden für eine Beschreibung dieses Verhaltens.

Warum wird mein __init__() nicht aufgerufen, wenn ich Objekte lade?

Siehe Nicht-zugeordnete Zustände bei Ladevorgängen beibehalten für eine Beschreibung dieses Verhaltens.

Wie verwende ich ON DELETE CASCADE mit dem ORM von SA?

SQLAlchemy gibt immer UPDATE- oder DELETE-Anweisungen für abhängige Zeilen aus, die sich derzeit in der Session befinden. Für nicht geladene Zeilen gibt es standardmäßig SELECT-Anweisungen aus, um diese Zeilen zu laden und zu aktualisieren/löschen; mit anderen Worten, es wird angenommen, dass kein ON DELETE CASCADE konfiguriert ist. Um SQLAlchemy zur Zusammenarbeit mit ON DELETE CASCADE zu konfigurieren, siehe Verwenden von ON DELETE cascade mit Fremdschlüsseln für ORM-Beziehungen.

Ich habe das Attribut „foo_id“ auf meiner Instanz auf „7“ gesetzt, aber das Attribut „foo“ ist immer noch None – hätte es nicht Foo mit der ID #7 laden sollen?

Das ORM ist nicht so aufgebaut, dass es eine sofortige Befüllung von Beziehungen unterstützt, die durch Änderungen von Fremdschlüsselattributen ausgelöst werden – stattdessen ist es so konzipiert, dass es umgekehrt funktioniert – Fremdschlüsselattribute werden vom ORM im Hintergrund behandelt, der Endbenutzer richtet Objektbeziehungen natürlich ein. Daher ist die empfohlene Methode zum Setzen von o.foo, genau das zu tun – es zu setzen!

foo = session.get(Foo, 7)
o.foo = foo
Session.commit()

Die Manipulation von Fremdschlüsselattributen ist natürlich völlig legal. Das Setzen eines Fremdschlüsselattributs auf einen neuen Wert löst derzeit jedoch kein „Expire“-Ereignis für die relationship() aus, an der es beteiligt ist. Das bedeutet, dass für die folgende Sequenz

o = session.scalars(select(SomeClass).limit(1)).first()

# assume the existing o.foo_id value is None;
# accessing o.foo will reconcile this as ``None``, but will effectively
# "load" the value of None
assert o.foo is None

# now set foo_id to something.  o.foo will not be immediately affected
o.foo_id = 7

o.foo wird beim ersten Zugriff mit seinem effektiven Datenbankwert von None geladen. Das Setzen von o.foo_id = 7 hat den Wert „7“ als ausstehende Änderung, aber kein Flush wurde durchgeführt – also ist o.foo immer noch None

# attribute is already "loaded" as None, has not been
# reconciled with o.foo_id = 7 yet
assert o.foo is None

Damit o.foo basierend auf der Fremdschlüssel-Mutation geladen wird, geschieht dies normalerweise natürlich nach dem Commit, der sowohl den neuen Fremdschlüsselwert leert als auch den gesamten Zustand ungültig macht.

session.commit()  # expires all attributes

foo_7 = session.get(Foo, 7)

# o.foo will lazyload again, this time getting the new object
assert o.foo is foo_7

Eine minimalere Operation ist das einzelne Ungültigmachen des Attributs – dies kann für jedes persistente Objekt mittels Session.expire() durchgeführt werden.

o = session.scalars(select(SomeClass).limit(1)).first()
o.foo_id = 7
Session.expire(o, ["foo"])  # object must be persistent for this

foo_7 = session.get(Foo, 7)

assert o.foo is foo_7  # o.foo lazyloads on access

Beachten Sie, dass wenn das Objekt nicht persistent, aber in der Session vorhanden ist, es als ausstehend bezeichnet wird. Das bedeutet, dass die Zeile für das Objekt noch nicht in die Datenbank eingefügt wurde. Für ein solches Objekt hat das Setzen von foo_id keine Bedeutung, bis die Zeile eingefügt ist; andernfalls existiert noch keine Zeile.

new_obj = SomeClass()
new_obj.foo_id = 7

Session.add(new_obj)

# returns None but this is not a "lazyload", as the object is not
# persistent in the DB yet, and the None value is not part of the
# object's state
assert new_obj.foo is None

Session.flush()  # emits INSERT

assert new_obj.foo is foo_7  # now it loads

Das Rezept ExpireRelationshipOnFKChange enthält ein Beispiel, das SQLAlchemy-Ereignisse verwendet, um das Setzen von Fremdschlüsselattributen mit Many-to-One-Beziehungen zu koordinieren.

Gibt es eine Möglichkeit, automatisch nur eindeutige Schlüsselwörter (oder andere Arten von Objekten) zu haben, ohne eine Abfrage für das Schlüsselwort durchzuführen und eine Referenz auf die Zeile zu erhalten, die dieses Schlüsselwort enthält?

Wenn Leute das Many-to-Many-Beispiel in der Dokumentation lesen, stellen sie fest, dass wenn sie dasselbe Keyword zweimal erstellen, es auch zweimal in die DB eingefügt wird. Was etwas unpraktisch ist.

Dieses Rezept UniqueObject wurde erstellt, um dieses Problem zu lösen.

Warum gibt `post_update` neben dem ersten UPDATE noch ein weiteres UPDATE aus?

Das `post_update`-Feature, dokumentiert unter Zeilen, die auf sich selbst verweisen / Wechselseitig abhängige Zeilen, beinhaltet, dass eine UPDATE-Anweisung als Reaktion auf Änderungen an einem bestimmten beziehungsgebundenen Fremdschlüssel ausgegeben wird, zusätzlich zu dem INSERT/UPDATE/DELETE, das normalerweise für die Zielzeile ausgegeben würde. Während der Hauptzweck dieser UPDATE-Anweisung darin besteht, sie mit einem INSERT oder DELETE dieser Zeile zu koppeln, um einen Zyklus mit einem wechselseitig abhängigen Fremdschlüssel zu brechen, wird sie derzeit auch als zweites UPDATE gebündelt, das ausgegeben wird, wenn die Zielzeile selbst einem UPDATE unterliegt. In diesem Fall ist die von `post_update` ausgegebene UPDATE-Anweisung **normalerweise unnötig** und erscheint oft verschwenderisch.

Einige Nachforschungen, um dieses „UPDATE / UPDATE“-Verhalten zu entfernen, zeigen jedoch, dass erhebliche Änderungen am Unit-of-Work-Prozess erforderlich wären, nicht nur in der `post_update`-Implementierung, sondern auch in Bereichen, die nichts mit `post_update` zu tun haben. Dies liegt daran, dass die Reihenfolge der Operationen auf der Nicht-`post_update`-Seite in einigen Fällen umgekehrt werden müsste, was wiederum andere Fälle beeinträchtigen kann, wie z. B. die korrekte Behandlung eines UPDATEs eines referenzierten Primärschlüsselwerts (siehe #1063 für einen Proof of Concept).

Die Antwort ist, dass „post_update“ verwendet wird, um einen Zyklus zwischen zwei wechselseitig abhängigen Fremdschlüsseln zu brechen, und damit dieses Zyklusbrechen nur auf INSERT/DELETE der Zieltabelle beschränkt ist, müsste die Reihenfolge der UPDATE-Anweisungen anderswo liberalisiert werden, was zu Fehlern in anderen Randfällen führt.