Die Flagge des Marasek

Dekostreifen

English

Aktuell Texte Der Comic Impressum Kalender Suche PHP-Klassen Container-Wizard main.s21

Kategorien

Buch
Computer
Computerspiele
Film
Geschichte
Gesellschaft
Idee
Kunst
Natur
Persönlich
Politik
Programmieren
Religion & Philosophie
Weblog
Weltpolitik
Weltsicht
{{login}}

Datenbanksünden

Permalink
Vorheriger: EclipseNächster: Hohe Telefonrechnung
Eingeordnet in: Computer, Programmieren

An dieser Stelle mal ein Exkurs zu Sünden, die man nie begehen darf, wenn man mit einer relationalen Datenbank arbeitet - die aber in meiner Erfahrung dennoch gang und gäbe sind.

Mangel an Normalisierung

Der Hauptfehler an Datenbankentwürfen ist ein Mangel an Normalisierung. Leute, es heisst nicht umsonst relationale Datenbank; relationale Datenbanken sind genau zu dem Zweck entwickelt worden, dass man Daten normalisiert.
Die erste Versuchung ist die, eine 1:1-Beziehung nicht zu normalisieren. Es ist einerseits eine schwere Sünde, weil es so leicht wäre, sie nicht zu begehen, aber andererseits eine mit noch relativ überschaubaren Folgen.
Ein Beispiel für eine 1:1 Beziehung wäre das "Herkunftsland" einer Person. Es ist offensichtlich, dass jede Person ein Herkunftsland hat. Wenn man nicht normalisiert, trägt man für jede Person das Land in ein Varchar-Feld ein - Datenredundanz ist die Folge. Dies ist noch relativ leicht zu handhaben, schliesslich kann man mit "select * from person where country = 'Germany'" das Land immer noch abfragen; und selbst alle verfügbaren Länder lassen sich noch mittels "select distinct country from person" noch relativ leicht erfassen. Diese Vorgehensweise erspart einen JOIN in Richtung "select * from person join country USING (co_id)". Besonders die früheren Versionen von MySQL erschwerten lange JOINs, so dass Normalisierung dort abschreckend war.

Probleme treten zu Tage, wenn die Nutzer der Datenbank sich nicht an Konventionen halten. England ist wohl das beliebteste Problemfeld: "England", "Great Britain" oder "United Kingdom". Schreibfehler mal beiseite gelassen, von Ländern mit schwierigeren Namen mal abgesehen. Uneinigkeit kann auch darüber herrschen, welche Sprache verwendet wird (Deutsch, Englisch, Eigenname).
Im Endeffekt steht man also mitunter mit "select * from person where country = 'Germany' or country = 'Deutschland' or country = 'FRG'" da.

Schlimm wird es allerdings, wenn die komplexeren Beziehungen auf diese Weise gespeichert werden - so zeigte mir ein Bekannter stolz seine "DVD-Datenbank". Nun hat er für jede DVD die beteiligten Schauspieler erfasst - eine klassische n:m-Relation. Ein Schauspieler kann in mehreren Filmen mitgewirkt haben und ein Film hat mehrere Schauspieler. Bei ihm gab es allerdings für die Schauspieler pro DVD-Datensatz ein Textfeld mit folgendem Inhalt:
"Bruce Willis, Arnold Schwarzenegger, Dustin Hoffman". An diesem Tag hörte er das Wort "Normalisierung" zum ersten Mal.

Dies ist die unsinnigste aller Varianten. Es gibt keine Möglichkeit mehr, die Datenbankfunktionen wie UPDATE, SELECT, ORDER, WHERE usw. auf diese Daten zu nutzen. Er muss die Daten auf der Applikationsebene verarbeiten, um beispielsweise einen Schauspieler hinzuzufügen, muss er den kompletten Datensatz holen, in einen Array verwandeln, den Schauspieler hinzufügen, in einen String verwandeln, zurückschreiben.

Komplexere Abfragen wie "in welchen Science-Fiction-Filmen der 90er hat Arnold Schwarzenegger mitgespielt" sind quasi unmöglich.

2. Sünde: Falsche Typen

Wenn man einen Wert in einer Datenbank abspeichert, sollte man den passenden Wert verwenden - INTEGER für Zahlen, VARCHAR für kurze Texte, BOOL für Ja/Nein, DATE für Datumswerte und so weiter. Das mag auf den ersten Blick trivial erscheinen, aber ich sah mich mal mit dem Fall konfrontiert, in dem ich die Sortierung einer Terminliste ändern sollte.
Tja...in der Datenbank stand tatsächlich ausgeschrieben "Donnerstag, der xx. Mai 2006" in einem VARCHAR-Feld. Damit standen alle Donnerstags-Termine natürlich in einer Gruppe. Nicht nur, dass man in diesem Fall nicht sortieren kann, auch Eingrenzungen wie "select * from termine where datum BETWEEN '2004-01-01' and '2004-12-31'" sind nicht machbar.

3. Sünde: das Frontend unnötig arbeiten lassen

Sofern man nicht direkt mit der Datenbank arbeiten möchte, wird man sich wohl ein Frontend programmieren. Dabei sollte das Frontend nie Arbeit erledigen, die von der Datenbank erledigt werden kann, beispielsweise...

  • Werte eingrenzen ("Alle Personen aus Deutschland")
  • Werte aufsummieren ("Summe der Ausgaben im letzten Jahr")
  • Minimal-, Maximal- und Durchschnittswerte bestimmen
  • Werte sortieren, in dem man z. B. alles erst in einen Array kopiert und diesen dann sortiert

Summieren und andere mathematische Geschichten sind noch ein Grenzfall - wenn ich die Daten ohnehin anzeigen lasse, kann ich sie beim durchlaufen auch summieren, zumal ich korrekterweise die Gesamtdaten und die gewünschte Summe in eine Transaktion packen müsste, damit beide Ergebnisse garantiert übereinstimmen.

4. Sünde: Funktionen für die Datenintegrität nicht nutzen

Auf älteren Versionen von MySQL konnte diese Sünde nicht begangen werden, schlicht, weil MySQL viele Funktionen nicht anbot. Mit MySQL 5.0 hat sich das aber geändert, und unter anderen Datenbanksystemen war diese Sünde schon länger möglich.
Es gibt diverse Dinge, die man nicht in seinen Daten sehen möchte - etwa übriggebliebene Kommentare zu längst gelöschten Texten oder etwa doppelte Werte, beispielsweise zweimal Germany in einer Länderliste.

  • Unique-Constraint
    Der Unique-Constraint verhindert, dass ein Feld innerhalb einer Tabelle zweimal den gleichen Wert zugewiesen bekommt. Es ist auch möglich, mehrere Felder einzutragen - beispielsweise möchte ich nicht, dass einem Film zweimal derselbe Schauspieler zugewiesen wird oder dass sich jemand für einen bestimmten Event zweimal anmeldet. Man kann dies zwar auch auf Applikationsebene abprüfen, dort geschehen jedoch eher mal Fehler.
  • Fremdschlüssel
    Fremdschlüssel gibt es in jeder "vernünftigen" Datenbank, bei MySQL muss man dazu den Tabellentyp "InnoDB" verwenden. Wenn z. B. für einen Text Kommentare erstellt wurden, ist es sinnvoll, diese zu löschen, wenn der Text gelöscht wird. Das kann man zwar auch auf Applikationsebene machen, geht aber das Risiko ein, dass Kommentare übrig bleiben, sollte das Skript mittendrin abstürzen.
    Auf der anderen Seite möchte man einen Text vielleicht genau dann nicht löschen können, wenn er noch Kommentare hat. Für beide Fälle kann man eine Fremdschlüsselrelation zwischen beiden Datensätzen erstellen und der Datenbank sagen, was geschehen soll, wenn der Elterndatensatz (der Text) gelöscht wird.
  • Transaktionen
    Ohne Transaktionen sollte man keine Bankgeschäfte erledigen - angenommen, ich überweise 100 € an meinen Energieversorger. Dazu werden 100 € von meinem Konto abgezogen und dem Konto des Versorgers gutgeschrieben. Was aber ist, wenn nach dem ersten Schritt der Server abstürzt - ohne Transaktion wären die 100 € weder auf meinem, noch seinem Konto.
    Jedwede Aktion, die man über mehrere Abfragen aufteilt, sollte als Transaktion realisiert werden, da innerhalb einer Transaktion der Zustand der Datenbank gleich bleibt.

Je nach Datenbank stehen noch sehr viel kompliziertere Funktionen zur Verfügung. An dieser Stelle ziehe ich aber persönlich die Grenze, da ich nicht von den Spezialfunktionen einer Datenbank abhängig sein möchte. Es kann aber mitunter sehr viel eleganter sein, die Datenbank eine Aufgabe erledigen zu lassen.

Kommentieren

Bitte beachten: Kommentare sind nicht sofort sichtbar, sondern werden erst nach einer kurzen Prüfung freigegeben, sofern keine rechtliche Beanstandung vorliegt.
Rechtlich bedenkliche Inhalte werden entweder entschärft oder nicht veröffentlicht.

* Titel  
* Nickname  
* Kommentar