News
DBA-Tipp: Hard Parses verringern – Performance steigern

Die Information ist ein schnelllebiges Gut. Jeden Tag werden wir mit hunderten Informationen zugemüllt. Deshalb sind wir bestrebt uns auf das Wesentliche zu konzentrieren und nur substantiell nachhaltige Informationen bereitzustellen.

Icon Unternehmen

Dass eine Oracle Datenbank performant läuft, hängt von vielen Faktoren ab. In diesem Artikel möchten wir Ihnen aufzeigen, welchen Einfluss ein guter Programmierstil auf die Performance der Datenbank haben kann.

Für die Ausführung von Code in der Datenbank analysiert (parses) die Oracle Datenbank diesen, generiert einen Hash-Wert und prüft, ob dieser Hash-Wert bereits im “library cache” (LC) vorhanden ist. Findet sich ein gleicher Hash-Wert und kann die zugehörige ausführbare Form des Codes nach weiteren internen Prüfungen wieder verwendet werden, nennt sich das “soft parse”.

Existiert kein gleicher Hash-Wert, wird dieser nebst geparstem Code in einer für die Datenbank ausführbaren Form im LC abgelegt, um anschließend ausgeführt zu werden. Dieser Vorgang wird als “hard parse” oder auch als “library cache miss” bezeichnet und verschlingt im Vergleich zum “soft parse” zusätzliche CPU- und “shared pool”-Ressourcen. Es spielt dabei keine Rolle, ob es sich bei dem Code um ein SQL-Statement, einen PL/SQL-Block oder Java-Code handelt.

Gibt es eine Übereinstimmung der beiden Hash-Werte, erfolgen weitere Prüfungen, ob sich der Code gleicht. Dabei wird jedoch nicht nur der logische Inhalt geprüft, sondern u.a. auch die Schreibweise des Codes. Anhand des folgenden Beispiels von SQL-Statements soll das dargestellt werden, denn für die Oracle Datenbank sind folgende Statements unterschiedlich:

select * from kunde where kdnr < 100;
select * from kunde where  kdnr < 100;
-- zusätzliches Leerzeichen
SELECT * FROM kunde WHERE kdnr < 100;
-- Groß-/Kleinschreibung
select * from kunde where kdnr < 101;
-- verschiedene Literale

Der aus den vier einzelnen Statements generierte Hash-Wert unterscheidet sich jeweils. Entsprechend werden vier ausführbare Formen generiert (hard parses), die letztlich das Gleiche erreichen wollen - die Ausgabe einer Menge x von Kunden gefiltert nach dem Wert der Spalte kdnr.

Durch die vier unterschiedlichen Hash-Werte wird mehr Speicherplatz im LC belegt. Andere Hash-Werte werden dafür unter Umständen aus dem LC entfernt. Neben den im Prinzip unnötigen hard parses für die drei 'zusätzlichen' SQL-Statements müssen später die Statements erneut analysiert werden (hard parses), die durch den erhöhten Speicherplatzbedarf zuvor entfernt wurden.

Alles in allem wird hier innerhalb der Datenbank ein erhöhter Analyseaufwand generiert und zwar nicht nur für sich sehr stark gleichende Statements, sondern auch für Statements mit logisch selbem Inhalt und auch Ergebnis.

Wie kann zusätzliche Analyse verringert oder vermieden werden?

Zunächst ist eine einheitliche Schreibweise der Statements angebracht, d.h. die einzelnen Statements sollten sich hinsichtlich ihrer Schreibweise in der Anwendung nicht unterscheiden. Die Datenbank selbst ignoriert die Groß-/Kleinschreibung ohnehin – bis auf Angaben in einfachen oder doppelten Anführungszeichen. Des Weiteren ist die Verwendung von sogenannten Bind-Variablen von enormem Vorteil. Obiges Statement könnte dann wie folgt aussehen:

select * from kunde where kdnr < :max_kdnr;

Das Statement wird beim ersten Mal analysiert, mit dem Hash-Wert im Cache abgelegt und bei Bedarf die ausführbare Form aufgerufen. Bei jedem weiteren Auftreten des Statements wird anhand des Hash-Wertes die abgelegte, ausführbare Form gesucht und – vorausgesetzt, sie befindet sich noch im Cache – ausgeführt; dabei spielt es keine Rolle, mit welchem Wert die Bind-Variable max_kdnr belegt ist. Somit werden Platz im LC und Ressourcen für das “hard parsing” gespart. Auch hier ist jedoch darauf zu achten, dass sich innerhalb der Anwendung - zum Beispiel in unterschiedlichen Code-Abschnitten - nicht der Name, der Wertebereich oder der Typ der Bind-Variablen ändert. Folglich ändert sich entsprechend auch der Hash-Wert und damit werden wieder für mehrere Versionen Hash-Werte abgelegt, wie z.B. in diesem Fall:

select * from kunde where kdnr < :max_kdnr;
select * from kunde where kdnr < :kdnr_max;

Das Setzen des Datenbankparameters CURSOR_SHARING=FORCE erzwingt die Behandlung des Statements so, dass Bind-Variablen anstelle von Literalen verwendet werden. Dies hat Einfluss auf die Verwendung von Ausführungsplänen. Denn unterscheiden sich mehrere Statements in der ursprünglichen Form nur durch Literale, wird nach der 'Transformation' der gleiche Ausführungsplan für die vorher unterschiedlichen Statements verwendet. Somit lassen sich auch hierüber ggfs. noch Leistungssteigerungen erreichen. Insgesamt ist die Verwendung von Bind-Variablen und dem Default-Parameter CURSOR_SHARING=EXACT dem CURSOR_SHARING=FORCE vorzuziehen, da die Transformation von der Literal- in die Bind-Variablen-Form und auch das Suchen ähnlicher Statements im Shared Pool wieder zusätzliche Ressourcen benötigt.

Es scheint also nur plausibel, dass unter Berücksichtigung dieser Punkte einige Prozente Performancegewinn erreichbar sind und sich entsprechender Programmierstil durchaus in Leistungssteigerung der Datenbank und damit der Anwendung bemerkbar macht.