Montag, 17. Februar 2014

Welche DR$-Tabellen gibt es und wozu sind sie gut?

Den meisten Anwendern fällt recht schnell auf, dass sich nach dem Erstellen eines Oracle TEXT-Index einige zusätzliche Tabellen im Datenbankschema befinden.
SQL> create index FT_PO on PURCHASEORDER_TAB (XML_DOCUMENT)
  2  indextype is ctxsys.context
  3  /

Index wurde erstellt.

SQL> select * from tab;

TNAME                          TABTYPE  CLUSTERID
------------------------------ ------- ----------
PURCHASEORDER_TAB              TABLE
DR$FT_PO$N                     TABLE
DR$FT_PO$R                     TABLE
DR$FT_PO$K                     TABLE
DR$FT_PO$I                     TABLE
:                              :
Diese Tabellen enthalten den eigentlichen Textindex - meist sind es vier Tabellen, wie wir noch sehen werden, können es aber durchaus mehr werden. Das Namensschema ist immer gleich.
  • Es beginnt mit dem Präfix DR
  • Bei nichtpartitionierten Indizes folgt ein "$", bei partitionierten Indizes ein "#"
  • Dann folgt der Name des Volltextindex
  • Bei einem partitionierten Index schließt sich eine "Partitions-ID" direkt an den Indexnamen an
  • Es folgt wieder ein "$"
  • Abschließend folgt ein Suffix, welches die genaue Aufgabe der Tabelle bezeichnet - im folgenden werden die einzelnen Tabellentypen näher erläutert
Ein Oracle TEXT Index besteht aber immer aus wenigstens vier Tabellen - in Oracle12c können es aber bis zu neun werden.

$I: Token-Tabelle

 ----------------------------------------- -------- ------------------
 TOKEN_TEXT                                NOT NULL VARCHAR2(64)
 TOKEN_TYPE                                NOT NULL NUMBER(10)
 TOKEN_FIRST                               NOT NULL NUMBER(10)
 TOKEN_LAST                                NOT NULL NUMBER(10)
 TOKEN_COUNT                               NOT NULL NUMBER(10)
 TOKEN_INFO                                         BLOB
 ----------------------------------------- -------- ------------------
Die Token-Tabelle ist die eigentliche Indextabelle. Sie speichert die Tokens, also die einzelnen Wörter, die sich durch die Zerlegung des Fließtexts ergeben haben, in der Spalte TOKEN_TEXT ab. Die Spalte TOKEN_INFO enthält (in binärer Form) die Informationen, in welchen Dokumenten, an welchen Stellen das jeweilige Token vorkommt. Die übrigen Spalten sind Hilfsspalten, damit das Interpretieren des BLOB effizienter wird.
Wird eine Volltextabfrage ausgeführt, so schaut Oracle TEXT zuerst in diese Tabelle - anhand der Suchbegriffe werden die BLOBs aus TOKEN_INFO ausgelesen und weiter verarbeitet.

$R: Mapping von DOCID auf ROWID

 ----------------------------------------- -------- ------------
 ROW_NO                                    NOT NULL NUMBER(3)
 DATA                                               BLOB
 ----------------------------------------- -------- ------------
Oracle Text arbeitet intern nicht mit ROWIDs als Zeiger auf die Tabellenzeilen, sondern mit DOCIDs. Bei der Indizierung erhält die erste Zeile die DOCID 1 und dann wird weitergezählt. Der Grund dafür ist, dass ein Volltextindex eine invertierte Liste ist - im Gegensatz zu einem "normalen" Index zeigen mehrere Einträge auf eine Tabellenzeile. Die Verwendung von ROWIDs würde zu extrem großen Indizes führen, so dass man die sparsameren DOCIDs verwendet. Die bereits erwähnte Spalte TOKEN_INFO in der $I-Tabelle liefert also DOCIDs zurück. Damit Oracle TEXT aber Tabellenzeilen zurückliefern kann, braucht es eine Mapping-Tabelle - anhand der $R-Tabelle kann nun zu jeder gefundenen DOCID die ROWID herausgesucht werden. Die ROWID zeigt dann schließlich auf die gewünschte Tabellenzeile. Normalerweise ist ein Oracle Text Index so aufgesetzt, dass diese Tabelle immer im Hauptspeicher residiert, also zur Abfragezeit keine I/O-Last generiert.

$K: Mapping von ROWID auf DOCID

 ----------------------------------------- -------- ----------------------------
 DOCID                                              NUMBER(38)
 TEXTKEY                                   NOT NULL ROWID
 ----------------------------------------- -------- ----------------------------
Die $K-Tabelle ist das "Gegenstück" zur $R-Tabelle - anhand einer gegebenen ROWID findet sie die für den Textindex relevante DOCID heraus. Für Abfragen ist diese Tabelle weniger wichtig; benötigt wird sie aber bei DML-Operationen auf bereits indizierte Zeilen - wird beispielsweise eine Zeile mit SQL DELETE gelöscht, dann braucht Oracle TEXT die DOCID, um das Löschen auch im Textindex zu vermerken.

$N: Negativliste - enthält gelöschte Dokumente

 ----------------------------------------- -------- ----------------------------
 NLT_DOCID                                 NOT NULL NUMBER(38)
 NLT_MARK                                  NOT NULL CHAR(1)
 ----------------------------------------- -------- ----------------------------
Bekanntlich arbeitet Oracle TEXT asynchron (der Parameter TRANSACTIONAL bleibt hier außer Acht). Eine SQL INSERT-Anweisung bewirkt, dass die ROWID der Tabellenzeile in die PENDING-Tabelle eingetragen und damit "zur Indizierung vorgesehen" wird (die tatsächliche Indizierung erfolgt durch eine SYNC-Operation). Eine SQL DELETE Anweisung bewirkt, dass die Datenbank die DOCID ermittelt ($K-Tabelle) und diese dann in die Negativliste einträgt. Bei Abfragen ermittelt Oracle TEXT die Treffermenge ganz normal und filtert dann die Einträge der Negativliste heraus. Ein SQL UPDATE wird wie ein DELETE, gefolgt von einem INSERT, behandelt. Das hat den interessanten Effekt, dass neu eingefügte Dokumente erst nach dem SYNC sichtbar werden, gelöschte dagegen sofort verschwinden und mit SQL UPDATE veränderte Einträge ebenfalls bis zum nächsten SYNC "unsichtbar" werden.

$P: Hilfstabelle für den Substring-Index

 ----------------------------------------- -------- ----------------------------
 PAT_PART1                                 NOT NULL VARCHAR2(61)
 PAT_PART2                                 NOT NULL VARCHAR2(64)
 ----------------------------------------- -------- ----------------------------
Der Substring-Index muss mit Hife der Wordlist-Preference explizit eingeschaltet werden und unterstützt Abfragen mit einem Wildcard auf der linken Seite. Sucht man bspw. nach dem Text %DATENBANK, so ist klar, dass die $I-Tabelle (Spalte TOKEN_TEXT) prinzipiell komplett geparst werden muss - anstelle des Wildcard können ja beliebige Zeichen stehen. Bei sehr großen Indizes kann das ein Problem sein - schließlich wird auch die $I-Tabelle nun sehr groß. Der Substring-Index legt die $P-Tabelle als Hilfstabelle an - das Token ORACLEDATENBANK würde in dieser Tabelle wie folgt abgelegt:
PAT_PART1            PAT_PART2
-------------------- --------------------
ORACLEDATENB         ANK
ORACLEDATEN          BANK
ORACLEDATE           NBANK
ORACLEDAT            ENBANK
:                    :
ORACLE               DATENBANK
:
Ist diese Tabelle vorhanden, so werden die Suchbegriffe mit einer Wildcard links zuerst anhand dieser Tabelle über die Spalte PAT_PART2 aufgelöst - mit der sich ergebenden Tokenliste wird danach normal verfahren. Die Größe der $P-Tabelle liegt typischerweise zwischen 10 und 20 Prozent der Größe des Textindex.

$S: Hilfstabelle für strukturierte Elemente (SDATA-Sections)

 ----------------------------------------- -------- ----------------------------
 SDATA_ID                                  NOT NULL NUMBER
 SDATA_LAST                                NOT NULL NUMBER
 SDATA_DATA                                         RAW(2000)
 ----------------------------------------- -------- ----------------------------
In der $S-Tabelle werden strukturierte Bestandteile des Index gespeichert - dies ist seit Oracle11g mit den Composite Domain Indexes möglich. Oracle TEXT erlaubt auf diesen SDATA-Section zusätzlich auch Suchen wie <, >, BETWEEN und andere. SDATA-Sections haben einen Datentypen - unterstützt ist neben VARCHAR2 auch NUMBER oder DATE, was für Abfragen wichtig ist (intern speichert die Tabelle die Daten im RAW-Format ab). SDATA-Sections können entweder durch die Klauseln FILTER BY und ORDER BY im CREATE INDEX-Kommando oder durch die Erstellung einer Section Group mit expliziten SDATA Sections deklariert werden.

$G: Automatic Near Realtime Index (Oracle12c)

 ----------------------------------------- -------- ----------------------------
 TOKEN_TEXT                                NOT NULL VARCHAR2(64)
 TOKEN_TYPE                                NOT NULL NUMBER(10)
 TOKEN_FIRST                               NOT NULL NUMBER(10)
 TOKEN_LAST                                NOT NULL NUMBER(10)
 TOKEN_COUNT                               NOT NULL NUMBER(10)
 TOKEN_INFO                                         BLOB
 ----------------------------------------- -------- ----------------------------
Die $G-Tabelle wurde mit dem neuen Feature Automatic Near Realtime Indexing in Oracle12c eingeführt; sie sieht genauso aus wie die Token-Tabelle ($I-Tabelle). Das Feature wurde bereits in einem Blog Posting näher beschrieben.

$E: Hilfstabelle für XML-Namespaces (Oracle12c)

 ----------------------------------------- -------- ----------------------------
 ID                                        NOT NULL VARCHAR2(12)
 NAMESPACE                                          VARCHAR2(4000)
 LNAME                                     NOT NULL VARCHAR2(256)
 NS                                                 VARCHAR2(100)
 ----------------------------------------- -------- ----------------------------
Ein weiteres neues Feature in Oracle12c ist die Unterstützung von XQuery Fulltext, also der Volltextsuche in XML-Dokumenten mit vollständiger Unterstützung des XML-Datenmodells. Die $E-Tabelle speichert dabei XML-Zusatzinformationen ab, die im "normalen" Volltextindex keinen Platz finden - hier sind vor allem die XML-Namespaces zu nennen, die denn auch vor Oracle12c nicht von Oracle TEXT unterstützt wurden. Das Feature wurde ebenfalls bereits in einem Blog-Posting näher beschrieben.

$D: Hilfstabelle für das Save-Copy Feature (Oracle12c)

 ----------------------------------------- -------- ----------------------------
 DOCID                                     NOT NULL NUMBER(10)
 ATTRIBUTES                                         BLOB
 DOC                                                BLOB
 CONFIG                                             VARCHAR2(2000)
 FLAG                                      NOT NULL NUMBER(5)
Auch das Save Copy Feature wurde in Oracle12c eingeführt. Ist es aktiviert, so speichert Oracle TEXT die Plaintext- oder gefilterte Version eines Binärformates wie PDF, DOC, PPT oder anderen in der $D-Tabelle ab. Zum Generieren von SNIPPETs, MARKUPs oder HIGHLIGHT-Dokumenten können dann die in dieser Tabelle abgelegten Kopien verwendet werden - früher musste das Originaldokument hierfür nochmals gefiltert werden, was sich natürlich negativ auf die Performance auswirkte ...

Montag, 2. Dezember 2013

Oracle12c: Unterstützung für XML-Namespaces in Oracle TEXT

Heute geht es nochmals um Oracle12c: Oracle TEXT hat ein hochinteressantes neues Feature dazubekommen: Und zwar die Unterstützung für XQuery Full Text. Damit bietet Oracle TEXT nun wirklich eine vollständige Unterstützung für XML. Aber Moment mal: Konnte Oracle TEXT nicht schon immer mit XML umgehen? Im Prinzip ja - aber einige XML-Besonderheiten wurden von Oracle TEXT nicht unterstützt - zum Beispiel XML Namespaces. Dazu ein kleines Beispiel: Zunächst füllen wir eine Tabelle mit ein paar XML-Dokumenten:
create table tab_xml (id number, docs xmltype)
/

/*
 * Das Dokument enthält ein Tag "tag" aus dem Namespace "http://mynamespaces.com/ns1"
 */
insert into tab_xml values (
  1, 
  '<ns1:dokument xmlns:ns1="http://mynamespaces.com/ns1" 
                 xmlns:ns2="http://mynamespaces.com/ns2">
    <ns1:tag att="attr1">Das ist ein Text</ns1:tag>
   </ns1:dokument>'
)
/

/*
 * Das Dokument enthält ein Tag "tag" aus dem Namespace "http://mynamespaces.com/ns2"
 * Also ein anderer Namespace als Dokument 1
 */
insert into tab_xml values (
  2, 
  '<ns1:dokument xmlns:ns1="http://mynamespaces.com/ns1" 
                 xmlns:ns2="http://mynamespaces.com/ns2">
    <ns2:tag att="attr1">Das ist ein Text</ns2:tag>
   </ns1:dokument>'
)
/

/*
 * Das Dokument enthält ein Tag "tag" aus dem Namespace "http://mynamespaces.com/ns2"
 * Also gleicher Namespace wie Dokument 2 - ABER: anderer Präfix!
 */
insert into tab_xml values (
  3, 
  '<ns_1:dokument xmlns:ns_1="http://mynamespaces.com/ns1" 
                  xmlns:ns_2="http://mynamespaces.com/ns2">
    <ns_2:tag att="attr1">Das ist ein Text</ns_2:tag>
   </ns_1:dokument>'
)
/

commit
/
Das Besondere an diesen Dokumenten sind die XML Namespaces. Die XML-Tags enthalten ein sogenanntes Namespace-Präfix. Das ist aber noch nicht der Namespace selbst - denn oben im ersten Tag wird dieser Präfix mit dem Attribut xmlns auf den tatsächlichen Namespace abgebildet. Das erste Dokument definiert also zwei Namespaces: http://mynamespaces.com/ns1 und http://mynamespaces.com/ns2. Das XML-Tag names tag trägt den Präfix ns1, gehört also zum Namespace http://mynamespaces.com/ns1. Im zweiten Dokument werden die gleichen Namespaxes und auch die gleichen Namespace-Präfixe verwendet - nur gehört das Tag tag hier zum anderen Namespace http://mynamespaces.com/ns2.
Im dritten Dokument kommt es nun: Auch hier werden die gleichen Namespaces verwendet, aber mit anderen Präfixen. Das XML Tag tag trägt nun den Präfix ns_2 - dieser ist aber auf den Namespace http://mynamespaces.com/ns2 gemappt - es ist also tatsächlich das gleiche Tag wie in Dokument 2.
In XML kann der gleiche Namespace tatsächlich mit unterschiedlichen Präfixen angesprochen werden; wenn man wissen möchte, ob zwei XML Tags identisch sind, reicht es also nicht aus, nur auf die Präfixe zu schauen; man muss nachsehen, auf welche Namespaces diese Präfixe gemappt wurden. Und genau das tut Oracle TEXT bis einschließlich Oracle11g nicht. Legen wir zunächst einen "klassischen" Oracle TEXT-Index mit "XML-Unterstützung" an.
create index ft_tabxml on tab_xml (docs)
indextype is ctxsys.context
parameters ('section group ctxsys.path_section_group')
/
Ein mit der PATH_SECTION_GROUP erzeugter Volltextindex eröffnet die Möglichkeit, mit den CONTAINS-Operatoren INPATH und HASPATH zu arbeiten. Diese unterstützen XML auch ganz wunderbar; man kann ganz ähnlich zu XPath arbeiten und auch Attribute werden unterstützt. Nur ... mit den Eigenheiten der XML-Namespaces kann die PATH_SECTION_GROUP in Oracle11g nicht umgehen ...
select id from tab_xml
where contains(docs, 'text INPATH(/ns1:dokument/ns2:tag)') > 0
/

        ID
----------
         2

1 Zeile wurde ausgewählt.
Es gibt von der Syntax her keine Möglichkeit, einen Namespace "richtig" anzugeben. Man kann nur den Namespace-Präfix angeben (und der wird so behandelt, als wäre er Teil des Tag-Namens). Aber Oracle TEXT kann in 11g nicht nachsehen, auf welchen Namespace er gemappt wurde und ob der gleiche Namespace noch von anderen Präfixen verwendet wird. Dokument 3 verwendet einen anderen Präfix, daher wird es nicht gefunden - es müsste aber gefunden werden, denn es ist das gleiche Tag. Doch Oracle12c schafft Abhilfe - zunächst muss der Index etwas anders erzeugt werden ...
begin
  ctx_ddl.create_section_group('my_sg_xquery', 'PATH_SECTION_GROUP');
  ctx_ddl.set_sec_grp_attr('my_sg_xquery', 'xml_enable', 'true');
end;
/
sho err

create index ft_tabxml on tab_xml (docs)
indextype is ctxsys.context
parameters ('section group my_sg_xquery')
/
Vor dem CREATE INDEX muss eine eigene Section Group vom Typ PATH_SECTION_GROUP erstellt werden. Das muss sein, denn bei dieser muss das Attribut XML_AWARE auf TRUE gestellt sein. Danach kann der Index normal erzeugt werden.
Nun liegt ein Textindex vor, der auch XML-Namespaces vollständig unterstützt. Nicht erweitert wurde dagegen die Syntax des CONTAINS-Operators - und das hat einen guten Grund: Denn für die Volltextsuche in XML-Dokumenten gibt es inzwischen einen Standard - XQuery Full Text - und diesen sollte man auch verwenden. Es machte also keinen Sinn, mit einer Erweiterung für CONTAINS neue, proprietäre Syntax zu schaffen, man entschied sich, auf den Standard zu setzen. Und der sieht so aus:
SELECT id
FROM tab_xml
WHERE XMLExists('declare namespace ns="http://mynamespaces.com/ns2";
                 //ns:tag[. contains text "text"]'
                  PASSING docs)
/
Achtung: Wenn Ihr mit SQL*Plus arbeitet, kann euch das Semikolon in der dritten Zeile in die Query kommen - SQL*Plus betrachtet die Abfrage als dort zu Ende. Das kann man mit einem set sqlterminator # umstellen. Diese Abfrage findet nun alles, was gefunden werden soll ...
        ID
----------
         2
         3

2 Zeilen ausgewählt.
Schauen wir nun mal in die Indexstrukturen hinein. Wie immer bei Oracle TEXT, ist der Index tatsächlich in den DR$-Tabellen abgelegt.
SQL> select tname from tab where tname like 'DR$%'
  2  /

TNAME
-------------------------------------------------------
DR$FT_TABXML$R
DR$FT_TABXML$N
DR$FT_TABXML$K
DR$FT_TABXML$I
DR$FT_TABXML$E
DR$FT_TABXML$D

6 Zeilen ausgewählt.
Die XML-Unterstützung ist nun in den $D- und $E-Tabellen enthalten. Die $E-Tabelle kann man lesen: Wie die folgende Abfrage zeigt, speichert Oracle TEXT dort alle XML-Tags mitsamt Ihren Namespaces ab.
SQL> select * from DR$FT_TABXML$E
/

ID           NAMESPACE                    LNAME        NS
------------ ---------------------------- ------------ ----------------------------
_-_1         http://mynamespaces.com/ns1  dokument     http://mynamespaces.com/ns1
_-_2         http://mynamespaces.com/ns1  tag          http://mynamespaces.com/ns1
_-_3         http://mynamespaces.com/ns2  tag          http://mynamespaces.com/ns2

3 Zeilen ausgewählt.
Die $D-Tabelle gehört eigentlich zum Save Copy Feature von Oracle TEXT, welches hier gar nicht explizit angesprochen wurde. In dieser Tabelle speichert Oracle TEXT, aktiviert man das Feature, gefilterte Versionen von Binärdokumenten ab, um Snippet- oder Markup-Operationen schneller durchführen zu können (auch dazu gibt es noch ein Blog Posting). Ein "XML-Aware" Textindex nutzt diese Tabellen aber ebenfalls für seine interne Verarbeitung. Die Spalte DOC der $D-Tabelle enthält eine nochmals aufbereitete Variante des XML-Dokumentes.
Zusammenfassend kann man also sagen, dass Oracle TEXT in der Version 12c eine umfangreiche und wirklich komplette Volltextsuche für XML anbietet - mit der Unterstützung für XQuery Full Text. Weitere Informationen zum Thema findet Ihr im XML DB Developers' Guide: Indexing XML Data for Full-Text Queries. Viel Spaß beim Ausprobieren.

Dienstag, 8. Oktober 2013

Oracle Text in Oracle 12c: Automatic Near Real-Time Index

Oracle Database 12c steht schon seit einiger Zeit zur Verfügung und wie schon angesprochen, wollen wir im Rahmen unserer Blogpostings die neuen Features nach und nach beschreiben. Dieses Mal geht es um das sogenannte Feature "Automatic Near Real-Time Indexing". Dabei geht es um die typische Anforderung den Index möglich aktuell zu halten - sogar bei hoher Änderungsrate. Hoher Aktualitätsanspruch bedeutet allerdings häufiges Synchronisieren und führt natürlich zu hoher Fragmentierung bzw. zu vermehrtem und länger andauernden OPTIMIZE Operationen.

12c löst dieses Dilemma mit dem neuen Konzept des "two-level" Index bzw. auch unter dem Feature Name "near real time index" bekannt. Dies erlaubt einen kleinen, fragmentierten Stage Index, der alle aktuellen Veränderungen enthält, vorzuhalten, ohne Änderungen an dem großen Index vornehmen zu müssen. Die Idee dabei ist, dass der Stage Index klein genug ist, um in die SGA zu passen. Die Daten können dann nach und nach vom Stage Index zum Hauptindex verlagert werden. Dies geschieht mit einem neuen MERGE Modus für Indizes.

Folgendes Beispiel zeigt wie das Feature genutzt werden kann. Zuerst stellen wir eine leere Tabelle zur Verfügung.
drop table texttabelle
/
drop sequence s
/
create sequence s
/
create table texttabelle(
  id          number(10) default s.nextval,
  dokument varchar2(1000))
/
Realisiert wird das Feature dann mit der zusätzlichen Storage Option STAGE_ITAB.
exec ctx_ddl.drop_preference ('my_storage');
exec ctx_ddl.create_preference('my_storage', 'BASIC_STORAGE');
exec ctx_ddl.set_attribute ('my_storage', 'STAGE_ITAB', 'true');
Nun erzeugen wir den Index.
create index my_index on texttabelle (dokument) 
indextype is ctxsys.context
parameters( 'storage my_storage sync (on commit)');
Überprüfen wir die erzeugten Tabellenobjekte, stellen wir fest, dass zusätzlich eine neue Tabelle mit Namen DR$MY_INDEX$G generiert wurde, die die gleiche Struktur wir die $I Tabelle besitzt.
SQL> select * from tab
  2  /

TNAME                          TABTYPE  CLUSTERID
------------------------------ ------- ----------
DR$MY_INDEX$G                  TABLE
DR$MY_INDEX$N                  TABLE
DR$MY_INDEX$R                  TABLE
DR$MY_INDEX$K                  TABLE
TEXTTABELLE                    TABLE
DR$MY_INDEX$I                  TABLE

6 rows selected.
Darüberhinaus werden weitere Objekte wie zum Beispiel ein $H B-tree Index auf der $G Tabelle erzeugt - dieser erfüllt die gleiche Aufgabe wie der $X Index auf der $I Tabelle.

Nun fügen wir einfach zwei Zeilen ein und schließen jeweils mit COMMIT ab.
insert into texttabelle (dokument) values 
         ('A-Partei gewinnt Wahl in Hansestadt');
commit;
insert into texttabelle (dokument) values 
         ('Terror in Nahost: Kriminalität steigt immer weiter an'); 
commit;
Überprüfen wir die Inhalte der beiden Tabellen, stellen wir fest, dass die $I Tabelle leer ist, wohingegen die $G Tabelle befüllt wurde. Neue Inhalte werden also nicht mehr direkt in der $I Tabelle geschrieben, sondern in der $G Tabelle mitgeführt.
SQL> select token_text from DR$MY_INDEX$I;
no rows selected

SQL> select token_text from DR$MY_INDEX$G;

TOKEN_TEXT
----------------------------------------------------------------
GEWINNT
HANSESTADT
IMMER
KRIMINALIT?T
NAHOST
PARTEI
STEIGT
TERROR
WAHL
WEITER

10 rows selected.
Um diese Tabelle in der SGA zu halten, kann man sich entweder auf das normale Caching Verhalten verlassen oder aber - falls konfiguriert - den KEEP Pool der Datenbank nutzen. Dazu kann man die folgenden Storage Attribute verwenden.
exec ctx_ddl.set_attribute ('my_storage', 'G_TABLE_CLAUSE', 
                                                  'storage (buffer_pool keep)');
exec ctx_ddl.set_attribute ('my_storage', 'G_INDEX_CLAUSE', 
                                                 'storage (buffer_pool keep)');
Möchte man nun die Daten in den Hauptindex manuell integrieren, kann man dazu das folgende OPTIMIZE Kommando verwenden.
execute ctx_ddl.optimize_index(idx_name=>'MY_INDEX', optlevel=>'MERGE');
Damit werden die Einträge aus $G in optimierter Form in die $I Tabelle überführt und gleichzeitig aus der $G Tabelle gelöscht.

Optimal wäre es nun, wenn dieser Aufgabe von Oracle automatisch durchgeführt werden könnte. Die neue Prozedur ADD_AUTO_OPTIMIZE erfüllt diese Aufgabe.
exec ctx_ddl.add_auto_optimize( 'my_index' )
PL/SQL procedure successfully completed.
Nach der Ausführung wird der Index zur automatischen Optimierung registriert, wie wir in der folgenden View sehen können.
SQL> select * from ctx_user_auto_optimize_indexes;

AOI_INDEX_NAME                 AOI_PARTITION_NAME
------------------------------ ------------------------------
MY_INDEX
Ausgeführt wird diese Aktion von einem DBMS_SCHEDULER Job, der automatisch im Hintergrund angelegt und ausgeführt wird.
SQL> select job_name, program_name, schedule_type, last_start_date 
     from dba_scheduler_jobs 
     where owner='CTXSYS';

JOB_NAME             PROGRAM_NAME         SCHEDULE_TYP
-------------------- -------------------- ------------
LAST_START_DATE
---------------------------------------------------------------------------
DR$BGOPTJOB          DR$BGOPTPRG          IMMEDIATE
08-OCT-13 04.42.13.977418 PM EUROPE/VIENNA
Übrigens lässt sich dieses Konzept des "two-level" Index - auch nachträglich mit einem ALTER INDEX REBUILD Kommando zu einem bestehenden Index hinzufügen.
alter index my_index rebuild parameters('replace storage my_storage');

Donnerstag, 29. August 2013

DBMS_PCLXUTIL zur Erzeugung von lokalen Indizes

Um die Performance beim Aufbau eines Index zu erhöhen, können Oracle Text Indizes mit dem Schlüsselwort PARALLEL aufgebaut werden. Geht es um den parallelen Aufbau eines lokalen partitionierten Index, kann allerdings auch das altbewährte Package DBMS_PCLXUTIL eine Alternative darstellen. Wer eine Beschreibung des Package sucht, wird im PL/SQL Packages Guide fündig; die Verwendung im Oracle Text Umfeld ist etwas versteckt im Oracle Text Reference Guide nachzuschlagen. Da es in letzter Zeit vermehrt Anfragen dazu gab, haben wir uns entschlossen, die Verwendung an einem Beispiel zu demonstrieren.
Zuerst zur Definition des Package: Das Package DBMS_PCLXUTIL sieht folgendermassen aus:
desc dbms_pclxutil
PROCEDURE BUILD_PART_INDEX
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 JOBS_PER_BATCH                 NUMBER                  IN     DEFAULT
 PROCS_PER_JOB                  NUMBER                  IN     DEFAULT
 TAB_NAME                       VARCHAR2                IN     DEFAULT
 IDX_NAME                       VARCHAR2                IN     DEFAULT
 FORCE_OPT                      BOOLEAN                 IN     DEFAULT
Die Verwendung von TAB_NAME und IDX_NAME muß sicherlich nicht erklärt werden. Anzumerken ist höchstens, dass man nur als Eigentürmer der Tabellen und des Index das Package verwenden kann; eine Benennung über eine zusätzliche Schemabezeichnung ist nicht möglich. Die beiden Parameter JOBS_PER_BATCH und PROCS_PER_JOB bestimmen dabei im Unterschied zu anderen Methoden die Parallelisierung auf zwei (!) Ebenen. JOBS_PER_BATCH ist für die sogenannte Inter Parallelität zuständig, die mit DBMS_JOB Prozessen realisiert wird, und PROCS_PER_JOB für die sogenannte Intra Parallelität, die mit parallelen Prozessen ausgeführt wird.

Genauer bedeutet dies:
JOBS_PER_BATCH steht für die Anzahl der Job Prozesse, die gleichzeitig arbeiten. Hier sollte gelten: Das Minimum ist 1, das Maximum stellt die Anzahl der Partitionen dar.

PROCS_PER_JOB bestimmt die Anzahl der parallelen Query Prozesse pro Job. Auch hier stellt die Zahl 1 das Minimum dar.

FORCE_OPT kann den Wert TRUE oder FALSE haben. FALSE führt nur ein REBUILD für UNUSABLE Indizes aus, TRUE hingegen für alle Partitionen.

Bevor wir starten, sollte zuerst die Einstellung der Job Prozesse geprüft werden. Der Initialisierungsparameter JOB_QUEUE_PROCESSES gibt die maximale Anzahl der Job Prozesse an.
show parameter job

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
job_queue_processes                  integer     1000
Wichtig zu wissen ist, dass die Prozedur BUILD_PART_INDEX davon ausgeht, dass die Data Dictionary Informationen zum Index schon existieren. Falls nicht erhält man folgende Fehlermeldung:
*
ERROR at line 1:
ORA-20001: Specified local index name 'PROD_DESC_IDX' does not exist
ORA-06512: at "SYS.DBMS_PCLXUTIL", line 301
ORA-06512: at line 1
Vorab ist also folgendes Kommando sinnvoll:
CREATE INDEX prod_desc_idx ON product_part (prod_desc) INDEXTYPE IS ctxsys.context LOCAL UNUSABLE;
Ab 11g Release 2 wird für UNUSABLE Index Partitionen übrigens kein Speicherplatz mehr verbraucht, so dass die Information nicht in USER_SEGMENTS verzeichnet ist. Die Data Dictionary Informationen sind aber wie immer in USER_INDEXES gespeichert.
SELECT * FROM user_segments WHERE segment_name='PROD_DESC_IDX';

no rows selected

SELECT index_name, index_type FROM user_indexes WHERE index_name LIKE 'PROD_DESC%'

INDEX_NAME           INDEX_TYPE
-------------------- ---------------------------
PROD_DESC_IDX        DOMAIN
Um eine Anwendung zu demonstrieren, erzeugen wir einen lokalen Text Index auf die Spalte PROD_DESC der partitionierten Tabelle PRODUCTS_PART mit den 4 Partitionen P_10, P_100, P_1000 und PARTMAXVALUE.
EXECUTE dbms_pclxutil.build_part_index
(JOBS_PER_BATCH => 4,
 PROCS_PER_JOB  => 1,
 TAB_NAME       => 'PRODUCT_PART',
 IDX_NAME       => 'PROD_DESC_IDX',
 FORCE_OPT      => TRUE); 
Parallel dazu lassen sich die Job Prozesse überwachen - es werden 4 Jobs gestartet jeweils mit Parallelität 1.
SELECT job, this_date, next_date, failures, what FROM dba_jobs;

       JOB THIS_DATE        NEXT_DATE        FAILURES
---------- ---------------- ---------------- ----------
WHAT
--------------------------------------------------------------------------------
        42                  27.08.2013 17:41
dbms_utility.exec_ddl_statement('alter index "SH"."PROD_DESC_IDX" rebuild partit
ion "P_100" parallel (degree 1)');

        43                  27.08.2013 17:41
dbms_utility.exec_ddl_statement('alter index "SH"."PROD_DESC_IDX" rebuild partit
ion "P_1000" parallel (degree 1)');

        44                  27.08.2013 17:41
dbms_utility.exec_ddl_statement('alter index "SH"."PROD_DESC_IDX" rebuild partit
ion "PARTMAXVALUE" parallel (degree 1)');

        41                  27.08.2013 17:41
dbms_utility.exec_ddl_statement('alter index "SH"."PROD_DESC_IDX" rebuild partit
ion "P_10" parallel (degree 1)');
Am Schluss sollte natürlich auch die erfolgreiche Indexerstellung überprüft werden.
SELECT err_timestamp, err_text FROM ctx_user_index_errors
ORDER BY err_timestamp DESC;

no rows selected
Möglich wäre allerdings auch folgendes Kommando mit insgesamt 2 Jobs - dabei jeweils 2 parallele Prozessen pro Job.
EXECUTE dbms_pclxutil.build_part_index
(JOBS_PER_BATCH => 2,
 PROCS_PER_JOB  => 2,
 TAB_NAME       => 'PRODUCT_PART',
 IDX_NAME       => 'PROD_DESC_IDX',
 FORCE_OPT      => TRUE);
Prüft man die Jobs, stellt man folgende Aufrufe fest.
SELECT job, this_date, next_date, failures, what FROM dba_jobs;

       JOB THIS_DATE NEXT_DATE FAILURES
---------- --------- --------- ----------
WHAT
--------------------------------------------------------------------------------
       221 28-AUG-13 28-AUG-13
dbms_utility.exec_ddl_statement('alter index "SH"."PROD_DESC_IDX" rebuild partit
ion "PARTMAXVALUE" parallel (degree 2)');

       222 28-AUG-13 28-AUG-13
dbms_utility.exec_ddl_statement('alter index "SH"."PROD_DESC_IDX" rebuild partit
ion "P_10" parallel (degree 2)');
Dann nach einer gewissen Zeit ...
SELECT job, this_date, next_date, failures, what FROM dba_jobs;

       JOB THIS_DATE NEXT_DATE FAILURES
---------- --------- --------- ----------
WHAT
--------------------------------------------------------------------------------
       241 28-AUG-13 28-AUG-13
dbms_utility.exec_ddl_statement('alter index "SH"."PROD_DESC_IDX" rebuild partit
ion "P_100" parallel (degree 2)');

       242 28-AUG-13 28-AUG-13
dbms_utility.exec_ddl_statement('alter index "SH"."PROD_DESC_IDX" rebuild partit
ion "P_1000" parallel (degree 2)');
Er laufen also immer zwei Jobs zu einer Zeit. Prüft man gleichzeitig die Parallelität über v$px_session und V$session, erkennt man, dass wirklich 2 Prozesse pro Job arbeiten.
Username     QC/Slave   Slave Set  SID    QC SID Requested DOP Actual DOP
------------ ---------- ---------- ------ ------ ------------- ----------
SH           QC                    34     34
 - p001      (Slave)    1          43     34                 2          2
 - p000      (Slave)    1          87     34                 2          2
SH           QC                    94     94
 - p003      (Slave)    1          95     94                 2          2
 - p002      (Slave)    1          98     94                 2          2
Wo liegt nun der Unterschied zwischen den beiden Läufen? Beim zweiten Lauf arbeiten mehrere -hier 2 - Prozesse pro Job gleichzeitig. Unter Umständen sind dadurch die Index Partitionen schneller erstellt, was bei großen Indizes mit langen Laufzeiten sicherlich wünschenswert ist. Allerdings ist folgendes zu beachten: Da wir mit mehreren Prozessen pro Job arbeiten, kann es zu einer höheren Fragmentierung der einzelnen Index Partitionen kommen. Hier wäre dann ein anschliessendes CTX_DDL.OPTIMIZE_INDEX nötig.

Zum Schluss vielleicht noch ein wichtiger Hinweis: Vergessen Sie bei all diesen Operationen die Memory Einstellung nicht. Die Überprüfung kann zum Beispiel über CTX_PARAMETERS erfolgen.
SELECT * FROM ctx_parameters WHERE par_name LIKE '%MEM%';

PAR_NAME                       PAR_VALUE
------------------------------ -----------------------------------
DEFAULT_INDEX_MEMORY           67108864
MAX_INDEX_MEMORY               1073741824
Da DBMS_PCLXUTIL ein Package zur allgemeinen Verwendung ist, sind keine Text spezifischen Einstellungen über den Package Aufruf möglich - also auch keine Memory Einstellungen. Möchte man eine spezielle Memory Einstellung verwenden, kann beispielsweise die Prozedur CTXSYS.CTX_ADM.SET_PARAMETER verwendet werden.

Montag, 26. August 2013

Alle 12c Oracle Text Features auf einen Blick!

Mit der neuen Oracle Database 12c Version sind einige interessante neue Features im Oracle Text Umfeld implementiert worden. Nach und nach wollen wir diese in unseren deutschsprachigen Postings thematisieren.

Wer jetzt allerdings schon einen Überblick über alle neuen Text Features erhalten möchte, kann entweder im
Oracle Text Application Developer's Guide 12c Release 1 (12.1) im Abschnitt New Features recherchieren oder sich über das neue Whitepaper New Features in Oracle Text with Oracle Database12c informieren.

Viel Spaß dabei!

Montag, 15. Juli 2013

Oracle TEXT in Oracle12c: Neues Feature 'Pattern Stopclass'

Heute festgestellt, dass das letzte Blog Posting schon fast 3 Monate zurückliegt. Nun wird es aber Zeit: Das neue Datenbankrelease Oracle12c bringt auch im Bereich Oracle TEXT einige neue Features mit sich - diese werden wir nach und nach in diesem Blog besprechen. Heute geht es um die neue Möglichkeit, eine Stopwortliste nicht nur mit einzelnen Wörtern, sondern mit Stop-Patterns zu versehen. Hierauf haben viele sicherlich schon lange gewartet. Ein Beispiel: Wir erzeugen eine Tabelle und füllen diese mit ein paar "Wörtern" ...
create table tab_stopclasstest (
  textcol  varchar2(200)
)
/

insert into tab_stopclasstest values ('200');
insert into tab_stopclasstest values ('100');
insert into tab_stopclasstest values ('99');
insert into tab_stopclasstest values ('Oracle TEXT');
insert into tab_stopclasstest values ('A100');
insert into tab_stopclasstest values ('01.09.2012');
Wenn es nun an die Definition der Stopwörter geht, so konnte man bislang mit CTX_DDL.CREATE_STOPLIST eine Stopliste erzeugen und mit CTX_DDL.ADD_STOPWORD die Wörter ("Oracle") hinzufügen.

begin
  ctx_ddl.create_stoplist(
    stoplist_name => 'NEUE_STOPLISTE'
  );
  ctx_ddl.add_stopword ('NEUE_STOPLISTE', 'Oracle');
  ctx_ddl.add_stopclass('NEUE_STOPLISTE', 'NUMBERS');
end;
/
Aber genau dabei beginnt in vielen Fällen das Problem: Denn wenn Zahlen nicht indiziert werden sollen, ist es nahezu unmöglich, im Vorfeld alle möglichen Varianten als Stopwörter zu bestimmen. Die Stopclasses machen nun genau das möglich (übrigens kann man die Stopklasse NUMBERS schon in Oracle 11.2 verwenden) - aber dort eben nicht mehr. Anders in Oracle12c ...
begin
  ctx_ddl.create_stoplist(
    stoplist_name => 'NEUE_STOPLISTE'
  );
  ctx_ddl.add_stopword ('NEUE_STOPLISTE', 'Oracle');
  ctx_ddl.add_stopclass('NEUE_STOPLISTE', 'NUMBERS');
  ctx_ddl.add_stopclass('NEUE_STOPLISTE', 'KLASSE_1','[A-Z]\d+');
end;
/
CTX_DDL.ADD_STOPCLASS nimmt drei Parameter entgegen. Der erste ist, wie schon bei ADD_STOPWORD, der Name der Stopliste. Danach kommt die "Stopklasse" - Oracle bringt eine vordefinierte Klasse mit: NUMBERS, die, wie gesagt, schon in 11.2 vorhanden ist. Ab Oracle 12.1 kann man aber auch einen anderen Namen eintragen - und dann braucht es noch den dritten Parameter: Dort wird ein regulärer Ausdruck hinterlegt, der die zu ignorierenden Wörter erfasst. In obigem Beispiel wäre das genau ein Buchstabe, gefolgt von mindestens einer Zahl. Legt man, basierend auf dieser Stopliste einen Index auf die obige Tabelle an, so wird dieser nur ein einziges Token enthalten: TEXT.
create index ft_stopclasstest on tab_stopclasstest (textcol)
indextype is ctxsys.context 
parameters ('stoplist neue_stopliste')
/

select token_text from dr$ft_stopclasstest$i
/

TOKEN_TEXT
-----------------------------------------
TEXT
Viel Spaß beim Ausprobieren.

Donnerstag, 18. April 2013

Indexstatistiken (INDEX_STATS) im XML-Format: Oracle TEXT Management einfach automatisieren!

Heute geht es um Statistiken für einen Oracle TEXT Index - vor einiger Zeit gab es dazu schon mal ein Posting - heute soll es darum gehen, wie man diese Statistiken so zur Verfügung stellt, dass man mit Reporting- oder Management-Tools einfach darauf zugreifen oder diese zur Automatisierung von Aufgaben verwenden kann. Indexstatistiken kann man mit CTX_REPORT.INDEX_STATS abrufen. In nahezu allen Beispielen (so auch im erwähnten Blog-Posting) werden die Statistiken als Textausgabe erzeugt - in etwa wie folgt ...
===========================================================================
                    STATISTICS FOR "TESTIT"."IDX_TEXT"
===========================================================================

indexed documents:                                                     10
allocated docids:                                                      10
$I rows:                                                               56

---------------------------------------------------------------------------
                             TOKEN STATISTICS
---------------------------------------------------------------------------
:
Dieses Format ist zwar gut lesbar, zur Automatisierung jedoch völlig ungeeignet. Allerdings bietet Oracle TEXT noch eine andere Variante zur Ausgabe der Statistiken an: XML - und das geht wie folgt.
DROP TABLE ausgabe
/

CREATE TABLE ausgabe (
  index_name  varchar2(200),
  zeitstempel date,
  resultat    xmltype
)
xmltype column resultat store as binary xml
/

 
declare
  ergebnis clob := null;
begin
  ctx_report.index_stats(
    index_name     => 'IDX_TEXT',
    report         => ergebnis,
    report_format  => ctx_report.fmt_xml,
    stat_type      => null
  );
  insert into ausgabe values ('IDX_TEXT', sysdate, xmltype(ergebnis));
  dbms_lob.freetemporary(ergebnis);
end;
/

 
set long 32000
set head off
set pagesize 10000

SELECT * FROM ausgabe
/
Das generierte Format sieht nun wie folgt aus ...
<CTXREPORT>
  <INDEX_STATS>
    <STAT_INDEX_NAME>"TESTIT"."IDX_TEXT"</STAT_INDEX_NAME>
    <STAT_INDEX_STATS>
      <STAT_STATISTIC NAME="indexed documents">10</STAT_STATISTIC>
      <STAT_STATISTIC NAME="allocated docids">10</STAT_STATISTIC>
      <STAT_STATISTIC NAME="$I rows">56</STAT_STATISTIC>
      :
Bei großen Indizes braucht der Aufruf von INDEX_STATS sehr lange - hier ist es sicher sinnvoll, mit Hilfe von DBMS_SCHEDULER einen Job zu erzeugen, welcher die Statistiken regelmäßig (bspw. über Nacht) aktualisiert. Das Interessante am XML-Format ist nun, dass es sich maschinell auswerten lässt. Dazu nutzen wir die XML-Funktionen in der Oracle-Datenbank und erstellen eine (relationale) View auf die XML-Ausgabe von CTX_REPORT.INDEX_STATS.
create or replace view view_index_stats as
select
  a.index_name,
  a.zeitstempel,
  x.indexed_documents,
  x.allocated_docids,
  x.dollar_i_rows,
  x.dollar_i_dsize,
  x.index_frag,
  x.garbage_docids,
  x.garbage_size
from 
  ausgabe a,
  xmltable(
    '/CTXREPORT/INDEX_STATS'
    passing a.resultat
    columns 
      indexed_documents number path '/INDEX_STATS/STAT_INDEX_STATS/STAT_STATISTIC[@NAME="indexed documents"]',
      allocated_docids  number path '/INDEX_STATS/STAT_INDEX_STATS/STAT_STATISTIC[@NAME="allocated docids"]',
      dollar_i_rows     number path '/INDEX_STATS/STAT_INDEX_STATS/STAT_STATISTIC[@NAME="$I rows"]',
      dollar_i_dsize    number path '/INDEX_STATS/STAT_FRAG_STATS/STAT_STATISTIC[@NAME="total size of $I data"]',
      index_frag        varchar2(10) path '/INDEX_STATS/STAT_FRAG_STATS/STAT_STATISTIC[@NAME="estimated row fragmentation"]',
      garbage_docids    number path '/INDEX_STATS/STAT_FRAG_STATS/STAT_STATISTIC[@NAME="garbage docids"]',
      garbage_size      number path '/INDEX_STATS/STAT_FRAG_STATS/STAT_STATISTIC[@NAME="estimated garbage size"]'
  ) x
/
Diese View lässt sich nun wie eine Tabelle verwenden ...
SQL> select * from view_index_stats

INDEX_NAME ZEITSTEMPEL          INDEXED_DOCUMENTS ALLOCATED_DOCIDS
---------- -------------------- ----------------- ----------------
DOLLAR_I_ROWS DOLLAR_I_DSIZE INDEX_FRAG GARBAGE_DOCIDS GARBAGE_SIZE
------------- -------------- ---------- -------------- ------------
IDX_TEXT   18.04.2013 14:43:49                 10               10
           56            198  0 %                    0            0


1 Zeile wurde ausgewählt.
Nicht so schön ist die Spalte INDEX_FRAG - denn das XML, welches von INDEX_STATS generiert wurde, enthält hier tatsächlich das Prozentzeichen - weshalb es nicht als NUMBER aufgefasst werden kann - hierfür müssen wir in der View-Definition noch ein wenig was tun ...
create or replace view view_index_stats as
select
  a.index_name,
  a.zeitstempel,
  x.indexed_documents,
  x.allocated_docids,
  x.dollar_i_rows,
  x.dollar_i_dsize,
  to_number(replace(x.index_frag, ' %', '')) as index_frag,
  x.garbage_docids,
 :
Danach ist auch die Spalte INDEX_FRAG vom Typ NUMBER und man kann nun numerische Vergleiche durchführen. Analog dazu lassen sich auch die Token-Statistiken entsprechend aufbereiten ... Dazu bauen wir eine zweite View ...
create or replace view index_stats_frag_tokens as
select
  a.index_name,
  a.zeitstempel,
  x.token_text,
  x.token_type,
  to_number(replace(x.token_frag, ' %', '')) as token_frag
from 
  ausgabe a,
  xmltable(
    '/CTXREPORT/INDEX_STATS/STAT_FRAG_STATS/STAT_TOKEN_LIST/STAT_TOKEN'
    passing a.resultat
    columns 
      token_text   varchar2(64) path '/STAT_TOKEN/STAT_TOKEN_TEXT',
      token_type   varchar2(50) path '/STAT_TOKEN/STAT_TOKEN_TYPE',
      token_frag   varchar2(20) path '/STAT_TOKEN/STAT_TOKEN_STATISTIC'
  ) x
/
  
select * from index_stats_frag_tokens
/
... deren Inhalt nun wie folgt aussieht ...
SQL> select * from index_stats_frag_tokens

INDEX_NAME ZEITSTEMPEL          TOKEN_TEXT           TOKEN_TYPE           TOKEN_FRAG
---------- -------------------- -------------------- -------------------- ----------
IDX_TEXT   18.04.2013 14:55:28  ZU                   0:TEXT                        0
IDX_TEXT   18.04.2013 14:55:28  WOLLEN               0:TEXT                        0
IDX_TEXT   18.04.2013 14:55:28  WIRTSCHAFT           0:TEXT                        0
IDX_TEXT   18.04.2013 14:55:28  WIRD                 0:TEXT                        0
IDX_TEXT   18.04.2013 14:55:28  WICHTIGER            0:TEXT                        0
IDX_TEXT   18.04.2013 14:55:28  WERDEN               0:TEXT                        0
IDX_TEXT   18.04.2013 14:55:28  WER                  0:TEXT                        0
IDX_TEXT   18.04.2013 14:55:28  WEITER               0:TEXT                        0
IDX_TEXT   18.04.2013 14:55:28  WAHLKAMPF            0:TEXT                        0
:
Lässt man das XML nun, wie schon erwähnt, per DBMS_SCHEDULER regelmäßig aktualisieren, so hat man (wie im Data Dictionary) bequem nutzbare Views mit Statistiken zum Index. Diese können nun natürlich auch für eine automatisierte Verarbeitung genutzt werden. So könnte ein Job regelmäßig alle Tokens optimieren (CTX_DDL.OPTIMIZE mit OPTLEVEL_TOKEN), die eine bestimmte Fragmentierung überschreiten. Gleiches gilt natürlich auf Indexebene.
Auch die Integration mit Management-Werkzeugen wie dem Oracle Enterprise Manager ist kein Problem. In der deutschsprachigen DBA Community ist beschrieben, wie man eine eigene "Metrik", basierend auf einer SQL-Abfrage einrichtet. Nimmt man hierfür die erstellten Views, so kann der Enterprise Manager einen Alert senden, sobald die Indexfragmentierung einen bestimmten Schwellenwert übersteigt. Die professionelle Wartung einer Oracle TEXT-Installation ist damit kein Problem mehr ...

Beliebte Postings