Volltextsuche

Aus php bar
Wechseln zu: Navigation, Suche

Zielsetzung

Das Ziel dieses Tutorials ist die Erstellung einer Volltextsuche auf Basis von PHP und MySQL. Es sollen hierbei nicht die von MySQL seit Version 3.23.23 bereit gestellten Volltext Indizes verwendet werden. Auch Tools wie ht://Dig werden keine Verwendung finden.

Es geht darum, mit MySQL eine flexible und einfache Volltextsuche auf Basis eines Indizes mit allen Suchworten zu realisieren. Natürlich lässt sich diese Vorgehensweise auch auf jede andere Datenbank adaptieren.

Vorüberlegungen

Wir gehen davon aus, dass der zu durchsuchende Datenbestand bereits in einer Datenbank (z.B. MySQL) vorliegt. Es ist kein Problem, wenn mehr als nur eine Datenbanktabelle in den Volltextindex aufgenommen werden sollen. So können verschiedene Tabellen für Artikel, Forenbeiträge oder Linkbeschreibungen mit der hier vorgestellten Methode in den Index aufgenommen werden.

Wichtig ist, dass jede Änderung eines Datensatzes der zu indizierenden Tabellen mit einem Zeitstempel gespeichert wird. Ändert also ein Nutzer einen Artikel oder wird ein neuer Forenbeitrag geschrieben, muss in die entsprechenden Datenbanktabellen das Datum mit Uhrzeit protokolliert werden. Alle verwendeten Zeitstempel sollten vom selben Spaltentyp sein, z.B. DATETIME oder TIMESTAMP.

Zudem sollen die Primärschlüssel der zu indizierenden Tabellen einigermaßen einheitlich gestaltet sein. Idealerweise werden auch hier die selben Spaltentypen verwendet, z.B. MEDIUMINT, um die spätere Performanz der Anwendung zu erhöhen.

Datenbankmodell

Zusätzlich zu dem bereits bestehenden Datenbankmodell benötigen wir drei weitere Datenbanktabellen, die im Folgenden kurz beschrieben werden. Die verwendeten Namen können selbstverständlich angepasst werden.

Tabelle search_word

Die Tabelle search_word enthält die einzelnen indizierten Wörter. Jedes Wort kann nur einmal in der Tabelle vorkommen, was durch die Einrichtung des UNIQUE KEY un_sw_word sicher gestellt wird. Stoppwörter werden bei der Indizierung nicht berücksichtigt und somit nicht in diese Tabelle aufgenommen.

CREATE TABLE search_word (
  sw_id mediumint(8) UNSIGNED NOT NULL AUTO_INCREMENT,
  sw_word varchar(32) NOT NULL DEFAULT '',
  PRIMARY KEY  (sw_id),
  UNIQUE KEY un_sw_word (sw_word)
) TYPE=MyISAM;


Die Spalte sw_word ist auf 32 Zeichen begrenzt, da die Unterscheidungskraft bei längeren Worten nicht mehr so groß ist. Beispiel: Das Wort "Unfallprämienrückgewährversicherung" wird nach 32 Zeichen abgeschnitten, so dass in der Tabelle nur "unfallpraemienrueckgewaehrversic" gespeichert wird. Sucht nun jemand nach dem Wort "Unfallprämienrückgewährversicherung" wird ein Artikel, der das Wort "Unfallprämienrückgewährversicherungsschein" enthält von den meisten Suchenden wahrscheinlich auch als relevanter Treffer angesehen.

Tabelle search_index

Die Tabelle search_index enthält die Verknüpfungen zwischen den Wörtern und den Tabellen, in denen die zu indizierenden Texte vorhanden sind. Die Spalte si_sw_id verweist per Fremdschlüssel auf unsere Tabelle search_word. Die Spalte si_count enthält die Anzahl der Fundstellen des Wortes in dem referenzierten Dokument.

Da wir nur einen Index für verschiedenen Tabellen verwenden möchten, verwenden wir für die Verknüpfung die beiden Spalten si_type und si_doc_id. Die Spalte si_type zeigt an, aus welcher Quelle die Daten stammen. In diesem Falle stehen Artikel, Forenbeiträge und Linkbeschreibungen zur Auswahl. Die Spalte si_doc_id enthält den Primärschlüssel der referenzierten Tabelle. Hier wird nun klar, warum die eingangs erwähnten Primärschlüssel möglichst vom selben Spaltentyp sein sollten. Damit es zu keinen Inkonsistenzen kommt, wird der Primärschlüssel der referenzierten Tabelle immer zusammen mit dem Typ gespeichert. Darüber wird dann noch der UNIQUE KEY un_si_type gelegt.

CREATE TABLE search_index (
  si_id mediumint(8) UNSIGNED NOT NULL AUTO_INCREMENT,
  si_sw_id mediumint(8) UNSIGNED NOT NULL DEFAULT '0',
  si_type enum('article','forum','link') NOT NULL DEFAULT 'article',
  si_doc_id mediumint(8) NOT NULL DEFAULT '',
  si_count smallint(5) UNSIGNED NOT NULL DEFAULT '0',
  PRIMARY KEY  (si_id),
  KEY fk_si_sw_id (si_sw_id),
  UNIQUE KEY un_si_sw_id (si_sw_id, si_doc_id)
) TYPE=MyISAM;


Tabelle search_time

Die Tabelle search_time dient dazu, den letzten Indizierungsvorgang für das referenzierte Dokument zu speichern. Ähnlich wie bei Tabelle search_index finden hier die Spalten st_type und st_doc_id Verwendung.

An dieser Stelle sollte nun auch klar werden, warum eingangs erwähnt wurde, dass alle zu indizierenden Datenbanktabellen einen Zeitstempel benötigen und der verwendete Spaltentyp möglichst überall gleich sein sollte. Bei gleichen Spaltentypen werden die SQL-Abfragen später einfacher.

CREATE TABLE search_time (
  st_id mediumint(8) UNSIGNED NOT NULL AUTO_INCREMENT,
  st_type enum('article','forum','link') NOT NULL DEFAULT 'article',
  st_doc_id mediumint(8) NOT NULL,
  st_date datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  PRIMARY KEY  (st_id),
  UNIQUE KEY un_st_type (st_type, st_doc_id)
) TYPE=MyISAM;


Datenbanktabelle für Artikel

Als Beispiel für die zu indizierenden Daten nehmen wir folgende Tabelle, die für die Artikel eines Content-Management-Systems (kurz CMS) verwendet werden könnte. Selbstverständlich lässt sich auch jede andere Datenbanktabelle, die Textdaten enthält für die Volltextindizierung verwenden.

Wir achten noch einmal darauf, dass der Primärschlüssel ca_id vom gleichen Spaltentyp ist wie si_doc_id bzw. st_doc_id und dass für den Zeitstempel der gleiche Spaltentyp genommen wird wie bei der Tabelle search_time. Die Spalte ca_status zeigt an, ob ein Artikel bereits für die Anzeige auf der Website freigeschaltet ist und somit in die Volltextsuche mit aufgenommen werden soll.

CREATE TABLE cms_article (
  ca_id mediumint(8) UNSIGNED NOT NULL AUTO_INCREMENT,
  ca_date datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  ca_status enum('blocked','approved') NOT NULL DEFAULT 'blocked',
  ca_title varchar(64) NOT NULL,
  ca_text text NOT NULL,
  ca_category varchar(64) NOT NULL,
  PRIMARY KEY  (ca_id)
) TYPE=MyISAM;


Indizierungsvorgang

Der Indizierungsvorgang erfolgt bei unserer Methode in zwei Hauptschritten, von denen der zweite Schritt nochmals in drei Teilschritte unterteilt wird. Im Überblick sieht dieser Vorgang so aus:

  • Zu indizierende Daten lesen
  • Indizierte Daten einzeln durchlaufen
    • Datensatz komprimieren
    • Stoppwörter entfernen
    • Index aktualisieren
      • Auf vorhandenen Index prüfen
      • Wortliste durchlaufen
      • Zeitstempel speichern

Im Folgenden werden die einzelnen Schritte kurz erläutert.

Zu indizierende Daten lesen

Im ersten Hauptschritt müssen die zu indizierenden Daten gelesen werden. Dies kann sich je nach der Datenquelle unterschiedlich gestalten. Die Daten aus einem CMS müssen eventuell anders selektiert werden als aus einem Forum. Als Beispiel verwenden wir unsere oben genannte Tabelle cms_article.

Wir müssen sicherstellen, dass wir nur Daten lesen, die seit der letzten Indizierung verändert worden sind bzw. die bisher noch gar nicht indiziert worden sind. Dafür verwenden wir eine SQL-Abfrage, welche die beiden Tabellen cms_article und search_time über einen LEFT JOIN miteinander verknüpft. Unsere Abfrage für die gezeigten Tabellen könnte in etwa so aussehen.

    SELECT ca_id AS st_doc_id, 
           st_id, 
           'article' AS st_type, 
           CONCAT(ca_title, ' ', ca_text, ' ', ca_category) AS search_data 
      FROM cms_article 
 LEFT JOIN search_time 
        ON st_doc_id = ca_id 
       AND FIND_IN_SET('article', st_type) 
     WHERE FIND_IN_SET('approved', ca_status) 
       AND (   st_id IS NULL 
            OR ca_date > st_date 
           ) 
  ORDER BY ca_date


Nach der Ausführung dieser SQL-Abfrage per PHP erhalten wir eine Ergebnismenge, die in etwa wie folgt aussehen könnte:

st_doc_id st_id st_type search_data
172 NULL article Meine Überschrift Hier steht dann der komplette Text des gesamten Artikels für die Volltextindizierung. Dieser Text wurde bisher noch nicht indiziert. Tutorial
70 17 article Noch eine Überschrift Dieser Artikel wurde schon einmal indiziert und in der Zwischenzeit aktualisiert. Deshalb gibt es eine st_id. Tutorial
170 NULL article Gedanken Ich bin klein, mein Herz ist rein, und dies hier soll ein Volltext sein. Gedicht

Datensätze, die bereits indiziert worden sind, haben in der Spalte st_id bereits einen Wert. Für alle neuen Artikel steht dort der Wert NULL. Mittels einer einfachen foreach-Schleife können wir die Ergebnismenge nun schrittweise durchlaufen und die nächsten Teilschritt nacheinander abarbeiten.


--

Datensatz komprimieren

Im ersten Teilschritt müssen die Daten der Spalte search_data kompimiert werden. Im einzelnen werden in diesem Schritt folgende Aufgaben durchgeführt:

Für die letzte Aufgabe verwenden wir einige reguläre Ausdrücke, welche die Umlaute 'Ä', 'Ö', 'Ü', das 'ß' und andere Sonderzeichen umwandeln bzw. entfernen.

 $locSearch[] = "==i";
 $locSearch[] = "=|=i";
 $locSearch[] = "=|=i";
 $locSearch[] = "=|=i";
 $locSearch[] = "=|||||=i";
 $locSearch[] = "=|||||=i";
 $locSearch[] = "=|||||=i";
 $locSearch[] = "=||||||=i";
 $locSearch[] = "=||||||=i";
10 $locSearch[] = "==i";
11 $locSearch[] = "==i";
12 $locSearch[] = "=([0-9/.,+-]*\s)=";
13 $locSearch[] = "=([^A-Za-z])=";
14 $locSearch[] = "= +=";
15     
16 $locReplace[] = "ss";
17 $locReplace[] = "ae";
18 $locReplace[] = "oe";
19 $locReplace[] = "ue";
20 $locReplace[] = "a";
21 $locReplace[] = "o";
22 $locReplace[] = "u";
23 $locReplace[] = "e";
24 $locReplace[] = "i";
25 $locReplace[] = "n";
26 $locReplace[] = "c";
27 $locReplace[] = " ";
28 $locReplace[] = " ";
29 $locReplace[] = " ";
30 
31 $outString = trim(strtolower(stripslashes(strip_tags($inString))));
32 $outString = preg_replace($locSearch, $locReplace, $outString );


Der verwendete reguläre Ausdruck kann leicht an eigene Bedürfnisse angepasst werden oder durch einen andere Umwandlungsroutine ersetzt werden. Wichtig ist, dass am Ende nur noch die Zeichenmenge [a-z] und das Leerzeichen vorhanden sind. Wer Zahlen benötigt, kann die entsprechenden Zeilen im regulären Ausdruck gerne entfernen.

Nach diesem Schritt sehen unsere Daten in etwa so aus:

st_doc_id st_id st_type search_data
172 NULL article meine ueberschrift hier steht dann der komplette text des gesamten artikels fuer die volltextindizierung dieser text wurde bisher noch nicht indiziert tutorial
70 17 article noch eine ueberschrift dieser artikel wurde schon einmal indiziert und in der zwischenzeit aktualisiert deshalb gibt es eine st id tutorial
170 NULL article gedanken ich bin klein mein herz ist rein und dies hier soll ein volltext sein gedicht

Stoppwörter entfernen

Im nächsten Teilschritt müssen wir alle Stoppwörter entfernen. Stoppwörter werden bei der Indizierung eines Textes entfernt, da sie in Texten sehr häufig vorkommen, ohne dass in der Regel mit Hilfe der Volltextsuche nach ihnen gesucht wird. Hier zwei Beispiele für Stoppwortlisten:

Das Entfernen der Stoppwörter ist relativ einfach. In unserem Beispiel gehen wir davon aus, dass sie bereits in einem Array vorliegen (siehe auch die beiden eben genannten Listen). Das Verfahren zum Entfernen dieser Stoppwörter kann wie folgt aussehen.

 // in $inStopwords ist ein numerisch indiziertes Array mit allen Stoppworten
 
 $locSearch[] = "=(\s[A-Za-z]{1,2})\s=";
 $locSearch[] = "= " . implode(" | ", $inStopwords) . " =i";
 $locSearch[] = "= +=";
 
 $locReplace[] = " ";
 $locReplace[] = " ";
 $locReplace[] = " ";
10 
11 $outString = " " . str_replace(" ", "  ", $inString) . " ";
12 $outString = trim(preg_replace($locSearch, $locReplace, $outString));


Die drei regulären Ausdrücke entfernen alle Worte mit weniger als 3 Buchstaben, entfernen die Stoppwörter und stellen sicher, dass nicht mehr als ein Leerzeichen hintereinander auftaucht. Vor der Ausführung der regulären Ausdrücke werden alle Worte durch zwei Leerzeichen getrennt, die später wieder entfernt werden. Dieser kleine Trick stellt sicher, dass direkt hintereinander folgende Stoppwörter auch gelöscht werden und dass Worte, die eines der Stoppwörter enthalten, nicht zerstückelt werden.

Dieser Vorgang lässt sich im Detail natürlich auch an die eigenen Bedürfnisse anpassen oder durch andere Routinen zum Entfernen von Stoppwörtern ersetzen.

Nach diesem Schritt sehen unsere Daten nun so aus. Durch das Entfernen der Stoppwörter ist gar nicht mehr so viel übrig geblieben, was indiziert werden muss.

st_doc_id st_id st_type search_data
172 NULL article ueberschrift steht komplette text gesamten artikels volltextindizierung text indiziert tutorial
70 17 article ueberschrift artikel einmal indiziert zwischenzeit aktualisiert tutorial
170 NULL article gedanken klein herz rein volltext gedicht

Index aktualisieren

Im letzten Teilschritt müssen wir nun den Index aktualisieren und die Datenbank aktualisieren. In diesem Schritt werden auch zum ersten Mal die anderen Spalten unserer gelesenen Datenmenge benötigt.

Auch das Erstellen des Index unterteilt sich in weitere Teilschritte.

Auf vorhandenen Index prüfen

Wenn der Datensatz einen Wert ungleich NULL in der Spalte st_id hat, bedeutet dies, dass er bereits indiziert worden ist. Um keinen allzu komplizierten Abgleich entwickeln zu müssen, wird der Index für diesen Datensatz entfernt. Für den zweiten Datensatz würde der SQL-Befehl wie folgt aussehen:

DELETE FROM search_index
      WHERE si_type = 'article'
        AND si_doc_id = 70


Wortliste durchlaufen

Nun wird die gesamte Wortliste Wort für Wort durchlaufen. Zudem erstellen wir ein leeres Array, welches die zu speichernden Daten enthalten wird. Für jedes Wort werden wiederum folgende Einzelschritte durchgeführt:

  • Kürze Wort, wenn länger als 32 Zeichen
  • Prüfe, ob Wort bereits in Tabelle search_word vorhanden
    • Falls ja, hole Schlüssel sw_id für dieses Wort
    • Falls nein, speichere Wort in Tabelle search_word und hole neuen Schlüssel sw_id für dieses Wort
  • Prüfe, ob es in unserem Datenarray bereits einen Eintrag für das Wort gibt
    • Falls ja, erhöhe den Zähler für dieses Wort um 1
    • Falls nein, erstelle neuen Eintrag in dem Datenarray für dieses Wort und setze den Zähler auf 1

Der ganze Vorgang könnte in etwa wie folgt aussehen:

 // in $inWords sind alle Wörter des Datensatzes vorhanden
 // in $inStType ist der aktuelle Wert der Spalte 'st_type' enthalten
 // in $inStDocId ist der aktuelle Wert der Spalte 'st_doc_id' enthalten
 
 $locData = array();
 
 foreach ($inWords as $locKey => $locVal)
 {
     if (strlen($locVal) > 32)
10     {
11         $locVal = substr($locVal, 0, 32);
12     }
13     
14     $locSwId = fetchWordId($locVal);  // holt Schlüssel bzw. speichert Wort neu in Datenbank
15     
16     if (isset($locData[$locSwId]))
17     {
18         $locData[$locSwId]["si_count"] = $locData[$locSwId]["si_count"] + 1;
19     }
20     else
21     {
22         $locData[$locSwId]["si_sw_id" ] = $locSwId;
23         $locData[$locSwId]["si_type"  ] = $inStType;
24         $locData[$locSwId]["si_doc_id"] = $inStDocId;
25         $locData[$locSwId]["si_count" ] = 1;
26     }
27 }
28 
29 sort($locData);


Im Array $locData haben wir nun alle Datensätze, die in die Tabelle search_index geschrieben werden können. Hierfür bietet es sich an, einen Multi-Insert zu verwenden, bei dem in einem Rutsch alle Daten in die Datenbank geschrieben werden können. Dies würde wie folgt aussehen:

INSERT INTO search_index
          ( si_sw_id, si_type, si_doc_id, si_count )
     VALUES 
          ( 1, 'article', 172, 2),
          ( 2, 'article', 172, 1),
          ( 3, 'article', 172, 1)


Zeitstempel speichern

Für das gerade neu indizierte Dokument muss nun nur noch der Zeitstempel in der Tabelle search_time gespeichert werden. War in unserem Datensatz bereits ein Wert für die Spalte st_id vorhanden, kann dieser aktualisiert werden. Im anderen Fall erfolgt für das Dokument und den Typ ein neuer Eintrag in der Tabelle.

Der Indizierungsvorgang ist somit abgeschlossen.

Suchvorgang

Der Suchvorgang gestaltet sich deutlich einfacher als der eben beschriebene Indizierungsvorgang. Auch hier erfolgt der Ablauf in mehreren Teilschritten.

  • Suchworte aufbereiten
  • Suchanfrage an Datenbank stellen
  • Suchergebnis ausgeben

Im Folgenden werden die einzelnen Schritte kurz erläutert.

Suchworte aufbereiten

Da wir die indizierten Texte nicht unbearbeitet in den Datenbestand des Volltextindizes aufgenommen haben, müssen wir auch die Suchworte aufbereiten. Hierbei können wir aber auf unser Wissen aus dem Indizierungsvorgang zurückgreifen. Im einzelnen müssen wir:

  • ggf. Suchparameter ermitteln
  • Suchworte komprimieren
  • Stoppwörter aus Suchworten entfernen

Der erste Teilschritt ist nur notwendig, wenn in der Suche auch Suchparameter wie die booleschen Operatoren AND und OR verwendet werden soll. In unserem Modell verwenden wir ausschliesslich eine Suche mit AND. Die anderen beiden Teilschritte sind noch aus dem Indizierungsvorgang bekannt.

Suchanfrage an Datenbank stellen

Kommen wir zum Kern der Suchanfrage. Aus den ermittelten Suchworten müssen wir nun eine SQL-Abfrage an die Datenbank zusammenbauen. Hier gibt es verschiedene Wege, je nachdem auf welche Datenquellen (CMS, Forum, Linkverzeichnis) wir zugreifen möchten oder ob wir im gesamten Index suchen möchten.

Suchanfrage nur für eine Datenquelle

Die Ergebnisse werden in unserem Beispiel nach der Anzahl der Treffer für das zu gesuchte Wort in dem entsprechenden Artikel sowie nach der letzten Aktualisierung sortiert.

Für jedes Suchwort benötigen wir einen Join mit den beiden Tabellen search_index und search_word. Deshalb müssen wir den Tabellen jeweils innerhalb der Abfrage einen Namen zuweisen, z.B. index_0 und index_1 bzw. search_0 und search_1 bei zwei Suchbegriffen.

Für den Join benötigen wir pro Suchwort vier weitere Bedingungen in der WHERE-Klausel, die mit AND miteinander verbunden werden. Diese vier Zeilen sind im folgenden Beispiel durch Leerzeilen vom Rest abgetrennt. Hier wird die Tabelle cms_article mit index_0 und index_0 mit word_0 "gejoint" und dann in Tabelle word_0 nach dem Wort gesucht.

Hier die Abfrage für ein Suchwort:

    SELECT cms_article.*, 
           index_0.si_count  AS si_count 
      FROM cms_article, 
           search_index AS index_0, 
           search_word  AS word_0 
     WHERE FIND_IN_SET('approved', ca_status) 

       AND index_0.si_doc_id = ca_id 
       AND FIND_IN_SET('article', index_0.si_type) 
       AND word_0.sw_id = index_0.si_sw_id 
       AND word_0.sw_word LIKE 'tutorial' 

  GROUP BY ca_id 
  ORDER BY si_count DESC, ca_date DESC


Suchanfrage für gesamten Index

Soll eine Abfrage über den gesamten Index erfolgen, können wir die Suchergebnisse nicht in einer SQL-Abfrage ermitteln. Wir müssen die Abfrage in mehrere Schritte unterteilen. Als erstes müssen wir die Dokumenten IDs und Dokumenttypen der Fundstellen ermitteln. Auch hier benötigen wir für jedes Suchwort einen separaten Join.

    SELECT index_0.si_doc_id AS si_doc_id, 
           index_0.si_type   AS si_type, 
           index_0.si_count  AS si_count 

      FROM search_index AS index_0, 
           search_word  AS word_0 

     WHERE 1

       AND word_0.sw_id = index_0.si_sw_id 
       AND word_0.sw_word LIKE 'tutorial' 

  GROUP BY si_doc_id 
  ORDER BY si_type, si_count DESC


Wird bei der Suche mehr als ein Suchwort abgefragt, dann muss die SQL-Abfrage entsprechend umgebaut werden.

    SELECT index_0.si_doc_id AS si_doc_id, 
           index_0.si_type   AS si_type, 
           index_0.si_count  AS si_count 

      FROM search_index AS index_0, 
           search_word  AS word_0, 

           search_index AS index_1, 
           search_word  AS word_1 

     WHERE 1

       AND word_0.sw_id = index_0.si_sw_id 
       AND word_0.sw_word LIKE 'tutorial' 

       AND index_0.si_doc_id  = index_1.si_doc_id 

       AND word_1.sw_id = index_1.si_sw_id 
       AND word_1.sw_word LIKE 'ueberschrift' 

  GROUP BY si_doc_id 
  ORDER BY si_type, si_count DESC


Wenn wir die Dokumenten IDs und Dokumenttypen haben, können wir gezielt die einzelnen Datenquellen (CMS, Forum, Linkverzeichnis) abfragen und uns die Daten holen, die wir für die Ausgabe brauchen.

Suchergebnis ausgeben

Die Ausgabe der Ergebnismenge ist recht trivial und kann nach Belieben erfolgen. Natürlich kann die Ergebnismenge bei Bedarf auch noch individuell umsortiert werden oder die Ausgabe der Liste auf mehrere Seiten verteilt werden. Hierbei müssen die SQL-Abfragen natürlich entsprechend mit der LIMIT-Klausel ergänzt werden.

Implementierung

Dieses Tutorial möchte keinen allgemeingültigen Weg für die Implementierung einer Volltextsuche aufzeigen. Es soll lediglich Anregungen liefern, mit denen fortgeschrittene PHP Entwickler ihre eigene individuelle Volltextsuche implementieren können.

Ob die Implementierung nun objektorientiert, prozedural oder gar in einem langen Spaghetti-Code-Skript erfolgt, bleibt jedem selbst überlassen. Gerade beim Indizierungsvorgang sollte auf Performanz der Skripte geachtet werden. Da kann ein langes Skript ohne Objektgenerierung und Funktionsaufrufe durchaus einen gewissen Geschwindigkeitsvorteil gegenüber eine komplett objektorientierten Lösung haben. Demgegenüber sollte der sicherlich höhere Wartungsaufwand des Spaghetti-Codes nicht unterschätzt werden.

Als praktikabelste Lösung hat es sich erwiesen, einen Cron-Job aufzusetzen, der einmal in der Nacht für alle neuen und geänderten Artikel den Index neu erstellt.

Erweiterungsmöglichkeiten

Es gibt für diese hier vorgestellte Volltextsuche noch eine Vielzahl von Erweiterungsmöglichkeiten. Zum einen sollte darauf geachtet werden, was mit Artikeln passiert, die freigeschaltet waren und wieder gesperrt werden. Diese sollten natürlich auch aus dem Index entfernt werden.

Auch muss die Volltextsuche angepasst werden, wenn die Website mehrsprachig ist. D.h. wir brauchen für jede Sprache einen eigenen Index, die natürlich die gleichen Datenbanktabellen verwenden können. Diese Tabellen müssten dann nur um einen Sprachschlüssel erweitert werden. Zusätzlich würden wir separate Stoppwortlisten für jede Sprache benötigen.

Fazit

Mit diesem Tutorial sollten fortgeschrittene PHP Programmierer in der Lage sein, eine individuelle Volltextsuche mit PHP und MySQL (oder einer anderen Datenbank ihrer Wahl) für eigene Projekte zu entwickeln. Es wurde nur die Methode erläutert und bewusst auf längere Code-Abschnitte oder Quellcode zum Download verzichtet.

Siehe auch