Gekonnt Redundanz vermeiden

Viele Sites im Web werden im Zuge des Web 2.0 immer mehr „driven by user content“ – die Community fügt also Beiträge hinzu und / oder kommentiert sie. Das bereitet auch beim Datenbank-Design mitunter Schwierigkeiten. Ich möchte am Beispiel einer Nachrichtencommunity erklären, welche Probleme hinsichtlich der Performance auftreten können, auch wenn man die Regeln des Entity Relationship Models konsequent angewendet hat und alles in der für die Praxis üblichen 3. Normalform ist.

Wir nehmen an, dass User selbst neue Artikel in die Datenbank hinzufügen können und dabei 0-n Bilder-URLs zu ihrem News-Artikel angeben können. Aus ERM-Sicht naheliegend wäre also eine 1:n-Beziehung von der Nachrichten-Tabelle auf die Bilder-Tabelle (1 Artikel kann mehrere Bilder haben, aber ein Bild gehört nur zu einem Artikel). Und hier kommt die Frage: Ist das wirklich die Beziehung, die uns performante Ergebnisse beschert oder schafft sie lediglich Redundanz (in Bezug auf eine solche Community)?

Wenn User URLs von Bildern angeben können, kann es auch sein, dass sie bei mehreren Artikeln die gleiche Bildurl verwenden. Und in genau diesem Fall entsteht Redundanz.
Besser wäre deshalb eine n:m-Beziehung zwischen Artikeln und Bildern, die über eine Zwischentabelle realisiert wird. Dadurch kann 1 Artikel mehrere Bilder haben, aber 1 Bild kann auch in mehreren Artikeln genutzt werden – und zwar ohne redundante Datenspeicherung.

Wie soll das nun aber beim Einfügen umgesetzt werden, schließlich schickt der User das Eintragsformular ab und dann muss schon alles eingetragen werden. Es gibt 2 Möglichkeiten: Entweder prüfen wir vor dem INSERT, ob die angegebene Bild-URL schon einmal vorhanden ist in unserer Bilder-Tabelle und verzweigen dann entweder zu einem INSERT des neuen Bildes und dem Eintrag in der Zwischentabelle oder eben nur zu dem Eintrag in der Zwischentabelle beziehend auf das schon vorhandene Bild.
Es geht allerdings sehr viel eleganter und weniger aufwendig: der INSERT-Befehl wird nämlich nur ausgeführt, wenn er gegen keinen PRIMARY- oder UNIQUE-Index verstößt. Zuerst legen wir deshalb einen UNIQUE-Index auf die Bild-URL, damit garantiert ist, dass auch wirklich keine Redundanz entsteht. Anschließend können wir über die Anwendung das gewünschte Ergebnis durch folgende Anweisung erhalten:

mysql_query("INSERT INTO bilder (bildurl) VALUES ('http://url.de/bild.jpg')");
mysql_query("INSERT INTO artikel_bilder (artikel_ID,bild_ID) VALUES (".$artikel_ID.",(SELECT ID FROM bilder WHERE bildurl='http://url.de/bild.jpg'))");

Die erste Query fügt das neue Bild nur hinzu, wenn nicht die gleiche URL bereits in der Datenbank vorhanden ist. Der zweite Befehl fügt dann den neuen Eintrag in der Zwischentabelle hinzu und holt sich die ID des gewünschten Bildes. Diese ID kann gleich der LAST_INSERT_ID() sein (nämlich, wenn das Bild noch nicht vorhanden war), muss aber nicht.

Wer insbesondere bei Text- oder Binärdatentypen darauf achten muss, dass keine doppelten Einträge vorkommen, kann allerdings keinen UNIQUE-Index verwenden (es geht schon, aber dann nicht auf den gesamten Text bzw. Blob sondern nur auf eine bestimmte Zeichen- bzw. Byteanzahl). In so einem Fall empfehle ich den Einsatz einer zusätzlichen Hash-Spalte, auf die man den UNIQUE-Index legt und in der man einen MD5-Hash des Textes / Binärinhaltes speichert (optimal wäre ein SHA1-Hash, da dieser überschneidungsfreier ist). Das aktualisieren dieses Hashes würde ich wiederum über einen Trigger lösen, damit der Hash auch nach Aktualisierungen stets aktuell ist. Wer eine MySQL-Version vor 5.0 verwendet, in der keine Trigger unterstützt werden, muss allerdings stets darauf achten, den Hash-Wert selbst (über die Anwendung) immer zu aktualisieren.

Jan hat 152 Beiträge geschrieben

2 Kommentare zu “Gekonnt Redundanz vermeiden

  1. Richard Ritschel sagt:

    Sehr schön, wieder etwas gelernt. Ein SELECT im INSERT – auf die Idee muss man erstmal kommen. 🙂

  2. kubi sagt:

    Hab’s bisher immer mit einer LAST_INSERT_ID gelöst, aber das sieht auch gut aus. Wobei man aufpassen muss, dass der Name der Bild-Datei „unique“ ist, sonst liefert einem das SELECT im INSERT mehrere Einträge zurück.

Eine Antwort schreiben

Ihre E-Mail-Adresse wird nicht veröffentlicht. Benötigte Felder sind markiert mit *

You may use these HTML tags and attributes: <a href=""> <blockquote cite=""> <pre lang=""> <b> <strong> <i> <em>