Oracle Text: Problem und Lösung

11.
November
2019
Veröffentlicht von: Alexander Karpovskiy

Muniqsoft Consulting unterstützt ihre Kunden bei allen Belangen rund um die Oracle Datenbank. Auch mit speziellen Themen, die grundsätzlich nicht zu den typischen DBA-Aufgaben gehören wie z. B. „Oracle Text“, sind wir vertraut. Dieser Tipp beschreibt den Lösungsweg eines Problemfalls, der bei einem Kunden aufgetreten ist. Wenn Sie bereits grundlegende Kenntnisse mit „Oracle Text“ haben, dann springen Sie bitte direkt zu Punkt 3. Für die anderen Leser erklären wir zuerst, was „Oracle Text“ ist und wie man damit umgeht. Unter dem letzten Punkt finden Sie Links auf weitere von uns verfasste Artikel zum Thema „Oracle Text“.

1 Was ist Oracle Text?

Stellen Sie sich vor, dass Sie ca. 1 Million Dokumente in Word- und PDF-Formaten haben. Diese Dokumente liegen im Filesystem eines Fileservers. Wie kann man eine Kontextsuche in diesem Chaos organisieren, ohne alle Dokumente neu zu verarbeiten bzw. manuell in der Datenbank einzulesen?

Oracle Fulltext Search ermöglicht das!

Sie können mit Hilfe der Search-Maschine alle Inhalte aller Dateien indizieren. Darauf aufbauend kann die Datenbank für Sie nach relevanten Dokumenten suchen. Zum Beispiel alle Dokumente, in denen „Stadt München“ steht.

2 Wie sehen Oracle Textindices aus DBA-Sicht aus?

Ein Textindex besteht aus mehreren internen Objekten und hat zahlreiche Einstellungen. Wie ein Index ganz genau definiert ist, kann mittels PL/SQL Packages „CTX_REPORT“ abgefragt werden.  Die folgende Abfrage liefert uns das fertige Skript für den kompletten Neuaufbau eines Indices.

SQL> select ctx_report.create_index_script('IM_XXX') from dual;

begin
  ctx_ddl.create_preference('"IM_XXX_DST"','DIRECT_DATASTORE');
end;
/

begin
  ctx_ddl.create_preference('"IM_XXX_FIL"','NULL_FILTER');
end;
/

begin
  ctx_ddl.create_section_group('"IM_XXX_SGP"','NULL_SECTION_GROUP');
end;
/

begin
  ctx_ddl.create_preference('"IM_XXX_LEX"','BASIC_LEXER');
  ctx_ddl.set_attribute('"IM_XXX_LEX"','BASE_LETTER','YES');
  ctx_ddl.set_attribute('"IM_XXX_LEX"','MIXED_CASE','NO');
  ctx_ddl.set_attribute('"IM_XXX_LEX"','INDEX_TEXT','YES');
  ctx_ddl.set_attribute('"IM_XXX_LEX"','INDEX_THEMES','NO');
  ctx_ddl.set_attribute('"IM_XXX_LEX"','ALTERNATE_SPELLING','GERMAN');
end;
/

begin
  ctx_ddl.create_preference('"IM_XXX_WDL"','BASIC_WORDLIST');
  ctx_ddl.set_attribute('"IM_XXX_WDL"','STEMMER','GERMAN');
  ctx_ddl.set_attribute('"IM_XXX_WDL"','FUZZY_MATCH','GERMAN');
end;
/

begin
  ctx_ddl.create_stoplist('"IM_XXX_SPL"','BASIC_STOPLIST');
end;
/

begin
  ctx_ddl.create_preference('"IM_XXX_STO"','BASIC_STORAGE');
  ctx_ddl.set_attribute('"IM_XXX_STO"','R_TABLE_CLAUSE','lob (data) store as (cache)');
end;
/

begin
  ctx_output.start_log('IM_XXX_LOG');
end;
/

create index "USR"."IM_XXX" on "USR"."XXXTEST" ("XXX")
  indextype is ctxsys.context
  parameters('
    datastore       "IM_XXX_DST"
    filter          "IM_XXX_FIL"
    section group   "IM_XXX_SGP"
    lexer           "IM_XXX_LEX"
    wordlist        "IM_XXX_WDL"
    stoplist        "IM_XXX_SPL"
    storage         "IM_XXX_STO"
  ')
/

begin
  ctx_output.end_log;
end;
/

Mittels ctx_ddl.create_preference werden die unterschiedlichen internen Eigenschaften für die Text-Suchmaschine definiert. „Datastore“ und „Storage“ teilen der Datenbank zum Beispiel mit, wo sich die zu durchsuchenden Dateien befinden und wie auf diese zugegriffen werden kann. Die Preference „Lexer“  sagt, welche semantische Regeln („German“ für die deutsche Sprache in unserem Beispiel) benutzt werden sollen.

Muss man aber wirklich alle diese Parameter bei jeder Indexerstellung explizit angeben? Natürlich nicht. Mit der folgenden, verkürzten Form wird normalerweise ein Textindex angelegt:

create index im_xxx on xxxtest(xxx) indextype is ctxsys.context
Parameters('lexer ctxsys.us_lexer stoplist ctxsys.no_stopword memory 400M')";

Soweit keine anderen Parameter direkt im Create-Statement mitgegeben werden, werden die bereits definierten Einstellungen verwendet. Man muss also die Parametrisierung für die ganze Oracle-Text-Maschine nur einmal durchführen.

Wie kann man die bereits konfigurierten Parameter sehen?

SQL> select * from ctx_parameters;

3 Troubleshooting

Was tun, falls Probleme mit Textindices auftreten?

Tipp: Logging einzuschalten.

Gehen wir zurück zu unserem Beispiel, der Befehl zum Aufbau des Indices mit Logging sieht wie folgt aus:

begin
  ctx_output.start_log('IM_XXX_LOG');
end;
/

create index "USR"."IM_XXX" on "USR"."XXXTEST" ("XXX")
  indextype is ctxsys.context
  parameters('
    datastore       "IM_XXX_DST"
    filter          "IM_XXX_FIL"
    section group   "IM_XXX_SGP"
    lexer           "IM_XXX_LEX"
    wordlist        "IM_XXX_WDL"
    stoplist        "IM_XXX_SPL"
    storage         "IM_XXX_STO"
  ')
/

begin
  ctx_output.end_log;
end;
/

Mittels der Prozedur ctx_output.start_log starten wir das Logging. Der Parameter „IMM_XXX_LOG“ steht für den Filenamen, unter welchem unser Logfile gespeichert wird. Den kompletten Verzeichnispfad findet man unter dem Parameter 'LOG_DIRECTORY' im 'CTX_PARAMETERS' View.
Wenn nötig kann der Verzeichnisname wie im folgenden Beispiel angepasst werden:

execute CTXSYS.CTX_ADM.SET_PARAMETER ('LOG_DIRECTORY','/tmp');

Mittels der Prozedur ctx_output.stop_log wird das Logging ausgeschaltet.

Schauen wir nun einmal so ein Logfile (in gekürzter Form) an. Was passiert beim Aufbau eines Oracle Textindices: 

Oracle Text, 11.2.0.1.0
16:34:24 10/07/19 begin logging
16:34:36 10/07/19 Current values of enabled traces:
16:37:22 10/07/19 populate index: USER.IM_XXX
16:37:22 10/07/19 Begin document indexing
16:37:22 10/07/19 100 documents indexed
16:37:22 10/07/19 200 documents indexed
16:37:22 10/07/19 300 documents indexed
….
16:59:02 10/07/19 17926000 documents indexed
16:59:02 10/07/19 17926100 documents indexed
16:59:02 10/07/19 17926200 documents indexed
16:59:02 10/07/19 Errors reading documents: 0
16:59:02 10/07/19 Index data for 3414826 documents to be written to database
16:59:02 10/07/19    memory use: 223254929
16:59:02 10/07/19 Begin sorting the inverted list.
16:59:03 10/07/19 End sorting the inverted list.
16:59:03 10/07/19 Writing index data ($I) to database.
16:59:03 10/07/19 Inserting using direct path loading
16:59:10 10/07/19 Wrote 857419 rows (9526 big rows) to $I.
16:59:10 10/07/19 Writing index data ($R) to database.
16:59:10 10/07/19    index data written to database.
16:59:10 10/07/19 End of document indexing. 17926216 documents indexed.
16:59:10 10/07/19 Writing index data ($K) to database.
17:00:48 10/07/19 Wrote 17926216 rows to $K.
17:00:48 10/07/19 Creating Oracle index "USER"."DR$IM_XXX$X"
17:00:53 10/07/19 Oracle index "USER"."DR$IM_XXX$X" created

4 Problem mit Neuerstellung des Indices

Einer unserer Kunden hatte folgendes Problem: in seinem Betrieb werden regelmäßig neue Applikations-Releases eingespielt. Dabei werden auch alle Textindices gelöscht und neu aufgebaut. Plötzlich kam bei der Erstellung eines neuen Textindices die folgende Fehlermeldung:

errMsg
"SQL execution error, ORA-29855: Fehler bei Ausführung der Routine ODCIINDEXCREATE
ORA-20000: Oracle Text-Fehler:
DRG-50857: oracle error in drvxtab.create_index_indexes
ORA-00955: Es gibt bereits ein Objekt mit diesem Namen
ORA-06512: in ""CTXSYS.DRUE"", Zeile 160
ORA-06512: in ""CTXSYS.TEXTINDEXMETHODS"", Zeile 366 "

Wie in Kapitel 3 beschrieben, schalteten wir das Logging ein. Dort wurde zwar kein Fehler protokolliert, aber es war ersichtlich, dass bei der Erstellung $X-Objekten der letzte Satz „Oracle index "USER"."DR$IM_XXX$X" created“ nicht im Log vorhanden war. Genau an dieser Stelle erhalten wir die oben beschriebene Fehlermeldung in der SQL-Plus Konsole.

Weitere Untersuchungen ergaben, dass ein Textindice aus mehreren internen Objekten besteht:

SQL> select object_name, object_type from user_objects where object_name like 'DR$IM_XXX%';
OBJECT_NAME     OBJECT_TYPE
--------------- ----------------------
DR$IM_XXX$X     INDEX
DR$IM_XXX$R     TABLE
DR$IM_XXX$N     TABLE
DR$IM_XXX$K     TABLE
DR$IM_XXX$I     TABLE

Der Problemindice wurde nochmals gelöscht und die Abfrage nochmal laufen gelassen:

SQL> drop index IM_XXX;
Index dropped.
SQL> select object_name, object_type from user_objects where object_name like 'DR$IM_XXX%';
OBJECT_NAME   OBJECT_TYPE
------------- -------------
DR$IM_XXX$X   INDEX

Offensichtlich wurde das X-Objekt (interner Index unseres Textindices) beim „drop index“ nicht vollständig entfernt. Deswegen wurde die Neuerstellung dieses Indexes abgebrochen, denn das Objekt mit dem gleichen Namen existiert ja bereits. Genau dies teilt uns auch die Datenbank mit der Fehlermeldung oben mit!

Die Lösung für das Problem?

Ganz einfach: das „Zombie-Objekt“ bereinigen.

SQL> drop index DR$IM_XXX$X;
Index dropped.

SQL> select object_name, object_type from user_objects where object_name like 'DR$IM_XXX%';
no rows selected

Und voilà!

SQL> create index im_XXX on XXXTEST(XXX) indextype is ctxsys.context Parameters('lexer ctxsys.us_lexer stoplist ctxsys.no_stopword memory 400M');
Index created.

Nach dieser „Reparatur“ hatte Oracle seine internen Objekte wieder im Griff und auch beim nächsten Release gab es keine Probleme mehr beim Neuanlegen des Textindices.

5 Fazit

Grundlegendes Verständnis über den Aufbau von Oracle Textindices ist wichtig. Und ebenso wichtig für das Eingrenzen von Fehlern ist die Nutzung der Logging-Funktion. Für weitere Fragen steht unser DBA-Team Ihnen gerne zur Verfügung.

6 Weitere Artikel zum Thema

Jede Menge Know-how für Sie!

In unserer Know-How Datenbank finden Sie mehr als 300 ausführliche Beiträge zu den Oracle-Themen wie DBA, SQL, PL/SQL, APEX und vielem mehr.
Hier erhalten Sie Antworten auf Ihre Fragen.