Archiv für das Tag 'Datenbank'

In Projekten wo mir die Datenbankstrukturen extern vorgegeben werden, bin ich von der Verwendung von Hibernate ziemlich weg gekommen.  Statt desssen leistet mir seit einiges Zeit iBatis sehr gute Dienste.

Da man ziemlich straight forward SQL Statements angegeben kann hat man das ganze sehr schnell am laufen, besonders spannend wird es aber, wenn man das Framework benutzt um auch dynamische Statements aufzubauen. Hier kann es einem viele Fallunterscheidung abnehmen und man kann mit einem Statement gleich sehr viele Fälle erschlagen.

Kürzlich bin ich auf das Blog von Steve McLeod gestoßen, der aus seiner Projekterfahrung ein ganz paar gute Tipps für (Java) Programmierer gesammelt hat.
Die meisten seiner Tipps möchte ich eigentlich jedem Programmierer an Herz legen. Einige Dinge sind sicherlich sehr speziell aber meine persönlichen Favortine sind:

  • Vorsicht mit Reflection – Das ist ein mächtiges Werkzeug aber man kann auch viel falsch machen
  • Versuche Klassen Immutable (unveränderlich zu machen) – Das führt zu einfachem Code und fast immer guter Performance
  • Schau immer erst in den Standard  Bibliotheken – Die Java Bibliotheken aus dem JDK oder auch von Apache Commons bieten viele Funktionalität, die man in der selben Qualität einfach nicht selber schreiben kann
  • Programmierer Defensiv – Es gibt aus meiner Sicht keine Echte Alternative zur defensiven Programmierung, besonders bei Projekt Code
  • Schreibe lesbaren Code – Kurze Methoden, kleine Klassen und lesbare if Statements sind der Schlüssel zu wartbarem Code
  • Performance liegt (fast) immer an der Datenbank – Kann ich komplett bestätigen, schlechte SQL Statement, fragmentierte Tabellen. Es gibt immer mal wieder schlechten Java Code, aber wenn es zu langsam ist, liegt es an der Datenbank.

Die volle Liste von Java Tipps findet sich im Solid Simple Safe Blog.

Nachdem ich im ersten Teil dieser kleinen Reihe geschrieben hatte, wie die Datenbankengine verschiedene Indixes benutzt, will ich in diesem zweiten Teil versuchen klar zu machen bei welchen Statement welcher Index überhaupt verwendet werden kann.
Auch hier will ich noch mal grob vereinfachen und mit einfachen Statements auf eine Tabelle beginnen.
Die Verarbeitungszeit steht ja praktisch immer in Bezug zu er Menge an Datensätzen, deswegen, versucht die Datenbank die Menge an zu verarbeitenden Daten möglichst schnell möglichst klein zu bekommen.

Im folgenden gehe ich von folgender Tabelle aus

Spaltenname Typ Index
EMPLOYEENO VARCHAR IDX_EMOLOYEENO AS UNIQUE INDEX ON EMPLOYEENO
NAME VARCHAR IDX_EMLOYEENAME AS INDEX ON NAME, FIRSTNAME
FIRSTNAME VARCHAR IDX_EMLOYEENAME AS INDEX ON NAME, FIRSTNAME
EMAIL VARCHAR IDX_EMLOYEEEMAIL AS INDEX ON EMAIL
JOB VARCHAR (kein Index)
SALARY NUMBER IDX_SALARY AS INDEX ON (SALARY)

Hier ein paar Faustregeln wann welcher Cache verwendet wird:

  • Hat eine Spalte in der SQL Abfrage einen Eindeutigen Index so wird dieser verwendet. Das gilt auch wenn noch andere mit AND verknüpfte Spalten in dem Statement auftauchen
    Beispiel: SELECT * FROM EMPLOYEE WHERE EMPLOYEENO=4711 AND NAME like 'Herbst%' verwendet immer den eindeutigen Index über die EMPLOYEENO.
  • Hat eine Spalte einen Index und wird mit einer Gleichheitsbedingung verwendet, so wird dieser Index herangezogen
    Beispiel: SELECT * FROM EMPLOYEE WHERE SALARY='32000' verwendet immer den eindeutigen Index über SALARY.
  • Abfragen nach Texten können einen Index nur bis zur ersten Wildcard verwenden.
    Beispiel: SELECT * FROM EMPLOYEE WHERE EMAIL LIKE 'joerg.herbst%' verwendet den Index über auf Email , da es sich um einen normalen Text Index handelt und die Wildcard an der rechten grenze steht.
    SELECT * FROM EMPLOYEE WHERE EMAIL LIKE '%@gish.de' verwendet keinen Index da die Wildcard links steht.
  • Ein mehrspaltiger Index kann nur verwendet werden wenn alle Index Spalten auch im Suchstatement vorhanden sind. Dabei werden die spalten von Links nach rechts ausgewertet.
    Beispiel:
    SELECT * FROM EMPLOYEE WHERE NAME='Herbst' AND FIRSTNAME='Joerg'
    verwendet den Index auf NAME.
    SELECT * FROM EMPLOYEE WHERE NAME='Mueller'
    verwendet den Index auf NAME.
    SELECT * FROM EMPLOYEE WHERE FIRSTNAME='Stephan'
    verwendet keinen Index.
    SELECT * FROM EMPLOYEE WHERE LASTNAME='Mueller' AND FIRSTNAME LIKE ='St%'
    verwendet den  Index.
  • Wendet man SQL Funktionen wie UPPER, LOWER etc. auf eine Tabellenspalte an kann grundsätzlich kein Index benutzt werden.
    Beispiel: SELECT * FROM EMPLOYEE WHERE LOWER(EMAIL)='max.muster@web.de' verwendet keinen Index und muss einen Full Table Scan machen.

In letzter Zeit bekomme ich immer wieder mal die Frage, warum denn eine Webanwendung so langsam ist, bzw. was man denn tun könnte, um sie schneller zu machen. Nach meiner Erfahrung sind Performance Probleme bei Webanwendungen zu über 50% Datenbankprobleme. Besonders unerfahrene Entwickler haben im Rahmen ihrer Ausbildung zwar häufig den SQL Syntax und Regeln zur normalisierung gelernt, aber selten wird auf die Auswirkungen eines bestimmten Statements auf die Ausführungsgeschwindigkeit eingegeben.
Der folgende Artikel soll daher mal in grob vereinfachter und abstrakter Weise die Vorgänge und die unterschiedlichen Auswirkungen in einer relationalen Datenbank erklären. Der Artikel soll dabei weder alles wissenschaftlich exakt sein, noch geht er auf Datenbank spezifische Details ein, sondern soll als Grundlage dienen um einen ersten Eindruck einer Datenbank zu bekommen.

Zugrifssmöglichkeiten

Versucht man mit Hilfe eines einfachen SQL wie zum Beispiel:

select customerno, firstname, lastname from customer where lastname='Ma%'

auf eine Datenbank zuzugreifen, hat die Datenbank grundsätzlich drei verschiedene Möglichkeiten:

  1. Die komplette Tabelle wird nach Zeilen durchsucht, für die die angegebene Bedingung zutrifft. (FULL TABLE SCAN)
  2. Ein Index kann verwendet werden, um die betreffenden Zeilen anzusprechen. (INDEX ACCESS)
  3. Ein Zugriff über einen Eindeutigen Schlüssel ermöglichkeit den Zugriff auf die Daten. (UNIQUE INDEX ACCESS)

Im folgenden gehe ich davon aus, dass der Zugriff auf alle Zeilen in der Datenbank gleich schnell erfolgt, und alle Vergleichsoperationen gleich viel Rechnezeit benötigen. Damit hängt die Verarbeitungsgeschwindigkeit nur noch von der Anzahl der zu prüfenden Zeilen ab. Wenn ein Statement schneller sein soll, muss es also gelingen die Anzahl der Vergleiche zu reduzieren:

  1. Beim Abgleich aller Zeilen muss die Datenbank grundsätzlich jede Zeile der Tabelle prüfen. Der Aufwand eines  Zugriffs ohne Index steigt also proportional mit der Anzahl der in der Tabelle.
  2. Nicht eindeutige Index Daten werden intern als Baum Struktur verwaltet. Dieser Baum ist sortiert so dass die Datenbank in der Wurzel mit der Suche beginnt und in jedem Knoten nur den Ast weiterverfolgen muss, der Zeilen mit der zutreffenden Bedingung beinhaltet.  Der Aufwand einen solchen Baum zu durchlaufen ist proportional zur Tiefe des Baumes und diese Ergebit sich aus dem Logarithmus der Anzahl der Zeilen der Tabelle. Der Aufwand beim Zugriff über einen nicht eindeutigen Index steigt also logarithmisch mit der der Anzahl der Zeilen.
  3. Für eindeutige Index Daten berechnet die Datenbank eine Hash Funktion. Mit Hilfe dieser Funktion kann Sie aus dem übergebenen Parameter genau die Adresse im Speicher berechnen an der sich der gesuchte Datensatz befindet.  Dabei ist es unabhängig ob es sich im eine sehr große oder sehr kleine Tabelle handelt.  Der Aufwand beim Zugriff über einen eindeutigen Index ist unabhängig von der Anzahl der Zeilen in der Tabelle.

Was bedeutet das nun in der Praxis? Wenn meine Kundentabelle 20 Millionen Einträge hat und das Vergleichen einer Tabellenzeile 0,1 Millisekunden dauert,  ergeben sich folgende Aufwände:

  • Zum Auslesen aller Tabellenzeilen muss ich zwanzig Million Zeilen lesen und vergleichen. Das dauert zwei Million Millisekunden, oder 2000 Sekunden oder ca. 30 Minuten.
  • Wenn ich einen nicht eindeutigen Index benutzen kann muss ln(20.000.000)= 17 Vergleiche durchführen, das dauert ca. 1,7 Millisekunden
  • Wenn ich einen eindeutigen Index habe, muss ich nur einen Vergleich durchführen, das dauert ca. 0,1 Millisekunden

Das ist natürlich eine extrem vereinfachte Bedtrachtung die nur zur Kennzeichnung der Dimensionen dienen soll. Ich habe hier weder Zeiten für die Wahl des korrekten Index, für die Auswertung der Index Inhalte oder den Hash Algorithmus berücksichtigt. Ausserdem legt eine Datebank oft auch schon interne temporäre Index oder Tabellendaten an, die in der Praxis definitiv zu anderen Zeiten führen werden.

Man kann also mit Hilfe eines Index bei großen Datenmengen Zugriffszeiten um gewaltige Dimensionen beschleunigen. Die Kunst ist es also Index Daten und SQL Statements so aufeinander abzustimmen, das beide optimal zusammenspielen. Tipps man SQL Statements in dieser Richtung optimiert, gibt es im zweiten Teil.