Im ausgesprochen lesenswerten SQL Performance Blog hat Paul White dieser Tage einige sehr interessante Artikel zum Halloween Problem und seinen Konsequenzen für den SQL Server geschrieben. Das angesprochene Phänomen ist ein klassisches Datenbank Problem, das seinen Namen erhielt, weil es erstmals an Halloween 1976 bei IBM beobachtet wurde und bei dem ein Update, das die Gehälter aller Angestellten, die weniger als 25000$ verdienten um 10% erhöhen sollte, so lange lief, bis jeder Mitarbeiter mindestens 25000$ erhielt - da das Problem festgestellt wurde, nehme ich allerdings nicht an, dass das Ergebnis eine praktische Konsequenz für die Gehaltsgruppe hatte. Ursache solcher Probleme sind Schleifen, die sich ergeben können, wenn das Update keinen eindeutig definierten Ausgangspunkt hat - also, wenn keine Lesekonsistenz auf Query-Ebene sichergestellt ist, wie sie z.B. durch Oracles Versionierungsstrategie gewährleistet wird. An dieser Stelle sei es mal wieder gesagt: ich denke, dass es eine ganze Reihe sehr guter RDBMS gibt, aber dass Oracle im Bereich von Lesekonsistenz und Transaktionssteuerung immer noch einen deutlichen Vorsprung gegenüber der Konkurrenz hat. Hier aber noch ein paar Anmerkungen zu den Artikeln des Herrn White:
The Halloween Problem – Part 1
Der SQL Standard verlangt drei vollständig getrennte Schritte bei der Durchführung von UPDATE-Operationen:
- A read-only search determines the records to be changed and the new column values
- Changes are applied to affected records
- Database consistency constraints are verified
Im SQL Server erfolgt die Verarbeitung aber aus Performance-Gründen iterativ und row basiert, so dass es Aufgabe des Optimizers ist, einen Plan zu finden, der keine logischen Probleme hervorruft. Gefährlich sind in diesem Zusammenhang in erster Linie Index-Zugriffe, bei denen die Änderung der Werte dazu führen würde, dass ein bereits berücksichtigter Satz mit seinem aktualisierten Wert "weiter hinten" im Index erneut auftaucht. Eine Möglichkeit zur Vermeidung einer Schleife ist der Einbau eines Zwischenschrittes TABLE SPOOL (EAGER SPOOL), der die Ergebnisse des ersten Schrittes temporär persistiert. Eine andere Variante ist die Verwendung des CLI beim Tabellenzugriff, sofern die entsprechenden Werte vom Update nicht betroffen sind.
Auch bei INSERT und DELETE spielt das Halloween-Problem eine Rolle. Bei einem INSERT, das die Zieltabelle auch als Datenbasis referenziert, sorgen wiederum TABLE SPOOL (EAGER SPOOL) Schritte dafür, dass die DML-Operation auf einer soliden Ausgangsbasis aufsetzt. Beim DELETE ist das Problem in vielen Fällen nicht relevant, da es keine Rolle spielt, ob ein Satz in einer Operation mehrfach gelöscht werden soll. Im Fall eines Self Joins in einer korrelierten Subquery kann es aber auch beim DELETE erforderlich sein, die Konsistenz der Basis über EAGER SPOOL sicherzustellen.
Für MERGE-Operationen gelten besondere Regeln und hier kommen spezielle Optimierungen ins Spiel. Ich halte es für wahrscheinlich, dass die komplexen Anforderungen der Lesekonsistenz für manche Instabilitäten der MERGE-Operationen im SQL Server verantwortlich sind. Da die angesprochenen Optimierungen relativ subtil sind, erspare ich mir an dieser Stelle eine umfangreiche Erläuterung.
The Halloween Problem – Part 4
Neben dem Spooling gibt es noch weitere Möglichkeiten, mit denen der Optimizer sicherstellen kann, dass die Ergebnisse der DML-Operationen konsistent bleiben: im Fall eines NL_Joins etwa ist ein EAGER SPOOL-Schritt erforderlich, aber ein HASH JOIN sorgt bereits der Aufbau der BUILT Tabelle für die einheitliche Basis, so dass keine temporäre Materialisierung von Zwischenergebnissen erforderlich ist. Gleiches gilt für den MERGE JOIN, bei dem die Sortierung dafür sorgt, dass keine Inkonsistenzen auftreten. Daraus ergibt sich allerdings, dass für vorsortierte Mengen nochmals eine - redundante - Sortierung durchgeführt wird, um das Halloween Problem zu vermeiden: eine NOSORT-Operation wie bei Oracle ist an dieser Stelle nicht möglich. Für den Optimizer besitzt jeder Operator Halloween Protection (HP) properties: eine required HP property und ein delivered HP property. Diese Eigenschaften werden bei der Plangestaltung berücksichtigt. Man kann das Verhalten des Optimizers über trace flags steuern - flag 8692 etwa erzwingt eine EAGER SPOOL Operation -, so dass sich die Performance-Auswirkungen unterschiedlicher Alternativen überprüfen lassen. Andere Fälle, in denen keine spezielle HP erforderlich ist, sind z.B. das Update mit Konstanten (bei denen keine Schleifen-Effekte auftreten können) - sofern die Konstanten als solche erkennbar sind (mit der Datenbank-Option FORCED PARAMETERIZATION kann man Literale durch Parameter ersetzen lassen: das Verfahren scheint also Oracles cursor_sharing = force zu entsprechen). Ein weitere Fall wäre das Update eines einzigen Satzes (TOP 1), wobei mir auf Anhieb nicht so ganz klar ist, wo man dergleichen ausführen möchte. Für die Isolation Level READ COMMITTED SNAPSHOT und SNAPSHOT ISOLATION (die in etwas Oracles Version von READ COMMITTED entsprechen) wäre HP eigentlich nicht relevant, aber da scheint die Implementierung noch ein paar Lücken zu haben.
Insgesamt macht mir die Serie noch einmal klar, welch dramatische Konsequenzen die unterschiedliche Interpretation von Lesekonsistenzregeln für die unterschiedlichen RDBMS mit sich bringt. Angesichts der großen Übereinstimmungen bei anderen Schlüsselkonzepten neige ich manchmal dazu, die Differenzen zu wenig zu beachten.
Keine Kommentare:
Kommentar veröffentlichen