Oracle Database Indexing Best Practices, Teil 3 - DOAG

Die Seite wird erstellt Till Nolte
 
WEITER LESEN
Oracle Database Indexing Best Practices, Teil 3 - DOAG
Oracle Database Indexing
Best Practices, Teil 3
                                                                            Randolf Geist, Unabhängiger Berater

Auch wenn die Version 19c der Oracle-Datenbank die automatische Indizierung als neues Feature mit
sich bringt, wird die Indizierung auch in Zukunft ein Dauerbrenner-Thema bleiben – auch schon aufgrund
der Lizenzpolitik seitens Oracle, die das neue Feature in naher Zukunft nur einem Bruchteil der Kunden
zukommen lassen wird.
Daher soll dieser Artikel mit einigen Mythen im Bereich Indizierung aufräumen und hilfreiche Tipps und
Tricks beschreiben, die helfen, besser zu verstehen, was im Bereich Indizierung sinnvoll und nützlich ist.
Im dritten und letzten Teil dieser Reihe schauen wir weiter auf Mehrspalten-Indizes und darauf, was es
diesbezüglich noch weiter zu beachten gilt.

                                                                                 Red Stack Magazin 01/2021        29
Datenbank

     Index-Komprimierung
                                                            alter index CONC_INDEX1_CODE_ID rebuild compress 1;

     Durch das Voranstellen von Spalten mit we-             SQL> SELECT index_name, blevel, leaf_blocks, clustering_factor
     nigen Ausprägungen kann noch ein anderes                 2   from user_indexes
     Feature zum Einsatz kommen, das wahr-                    3   where index_name = 'CONC_INDEX1_CODE_ID' or index_name = 'CONC_
     scheinlich zu den am wenigsten genutzten,              INDEX1_ID_CODE';
                                                            INDEX_NAME			BLEVEL		LEAF_BLOCKS              CLUSTERING_FACTOR
     kostenlosen (Enterprise Edition) Features
                                                            -------------------- --------   ------------  -----------------
     der Datenbank gehört: die sogenannte In-
                                                            CONC_INDEX1_CODE_ID		    2		    2924			1000000
     dex Compression. Diese gibt es schon seit              CONC_INDEX1_ID_CODE		    2		    3306			1000000
     der Version 8 von Oracle als „Basic Index
     Compression“, die keine weitere Lizenz über          Listing 1: Kompressionseffekt nur mit Angabe der führenden Spalte
     die Enterprise Edition hinaus benötigt. Seit
     der Version 12c gibt es zusätzlich eine „Ad-
     vanced Index Compression“, die eine „Ad-             kann trotzdem die Größe einer Index-              ten für die Kompression verwendet, kann
     vanced Compression“-Lizenz voraussetzt.              Struktur signifikant verkleinern.                 sich der Effekt ins Gegenteil verkehren: Bei
        Die „Basic Index Compression“ ersetzt                 Der maßgebliche Unterschied zwi-              sich entsprechend wenig wiederholenden
     in den Index Leaf Blocks führende Werte,             schen „Basic Index Compression“ und der           Werten wird so viel Platz für Platzhalter und
     die sich wiederholen, mit einem kürzeren             kostenpflichtigen „Advanced Index Com-            Wertspeicherung verbraucht, sodass der
     Platzhalter/Symbol, und speichert den                pression“ besteht darin, dass man bei der         Index größer anstatt kleiner werden kann.
     sich wiederholenden Wert nur einmalig                „Basic“-Variante die Anzahl der sinnvoll zu       Insofern sollte man sich hier gut überle-
     im Block ab. Je größer also der Längenun-            komprimierenden führenden Spalten selbst          gen, welche Anzahl von Spalten Sinn ergib.
     terschied zwischen Wert und Platzhalter,             bestimmen muss und dies von Oracle be-            Oracle kann diesen Wert tatsächlich auch
     desto effektiver der „Kompressionsfak-               dingungslos umgesetzt wird – gibt man kei-        selbst ermitteln, leider ist dies aber um-
     tor“. Durch diese simple Implementie-                nen Wert an, komprimiert Oracle alle Spal-        ständlich: Mithilfe der Ausführung des Be-
     rung erzeugt diese Art des Platzsparens              ten (außer bei UNIQUE-Indizes, hier werden        fehls ANALYZE INDEX … VALIDATE STRUC-
     so gut wie keinen messbaren Overhead                 alle bis auf die letzte Spalte komprimiert).      TURE wird der spezielle View INDEX_STATS
     beim Suchen und Pflegen des Index und                Wird hier eine unpassende Anzahl von Spal-        befüllt – und dieser beinhaltet neben vielen

      select * from conc_index1 a where code = 2 and id2 = 4243;

        ID			      CODE		     ID2		  FILLE
      -------- ---------- ---------- -----
        4242		     2			       4243    x
        4242		     2			       4243    x

      -----------------------------------------------------------------------------------------------------------
      | Id | Operation                            | Name                | Rows | Bytes | Cost (%CPU)| Time      |
      -----------------------------------------------------------------------------------------------------------
      |   0 | SELECT STATEMENT                    |                     |     2 |    38 |   593   (1)| 00:00:01 |
      |   1 | TABLE ACCESS BY INDEX ROWID BATCHED| CONC_INDEX1          |     2 |    38 |   593   (1)| 00:00:01 |
      |* 2 |    INDEX RANGE SCAN                  | CONC_INDEX1_CODE_ID |     2 |       |   591   (1)| 00:00:01 |
      -----------------------------------------------------------------------------------------------------------

      Predicate Information (identified by operation id):
      ---------------------------------------------------
       2 - access("CODE"=2 AND "ID2"=4243)
      		    filter("ID2"=4243)

      Statistics
      ----------------------------------------------------------
      			        0 recursive calls
      			        0 db block gets
      			596 consistent gets
      			        0 physical reads
      			        0 redo size
      			 565 bytes sent via SQL*Net to client
      			 371 bytes received via SQL*Net from client
      			        2 SQL*Net roundtrips to/from client
      			        0 sorts (memory)
      			        0 sorts (disk)
      			 2 rows processed

     Listing 2: Kompressionseffekt mit Zugriff auf die erste und dritte Spalte

30     www.aoug.at • www.doag.org • www.soug.ch
CREATE INDEX conc_index1_id_id2 ON conc_index1
 (id, id2);
 SQL> SELECT index_name, blevel, leaf_blocks, clustering_factor
   2   from user_indexes
   3   where table_name = 'CONC_INDEX1'
   4   ;

 INDEX_NAME			BLEVEL		LEAF_BLOCKS            CLUSTERING_FACTOR
 -------------------- ---------- ----------- -----------------
 CONC_INDEX1_ID_ID2   2			       2919			1000000

 SQL> select * from conc_index1 a where id = 10000 and id2 = 10001;

 ----------------------------------------------------------------------------------------------------------
 | Id | Operation                            | Name               | Rows | Bytes | Cost (%CPU)| Time      |
 ----------------------------------------------------------------------------------------------------------
 |   0 | SELECT STATEMENT                    |                    |     2 |    38 |     5   (0)| 00:00:01 |
 |   1 | TABLE ACCESS BY INDEX ROWID BATCHED| CONC_INDEX1         |     2 |    38 |     5   (0)| 00:00:01 |
 |* 2 |    INDEX RANGE SCAN                  | CONC_INDEX1_ID_ID2 |     2 |       |     3   (0)| 00:00:01 |
 ----------------------------------------------------------------------------------------------------------

 Predicate Information (identified by operation id):
 ---------------------------------------------------

     2 - access("ID"=10000 AND "ID2"=10001)

 Statistics
 ----------------------------------------------------------
 			 0 recursive calls
 			 0 db block gets
 			 6 consistent gets
 			 0 physical reads
 			 0 redo size
 			 563 bytes sent via SQL*Net to client
 			 372 bytes received via SQL*Net from client
 			 2 SQL*Net roundtrips to/from client
 			 0 sorts (memory)
 			 0 sorts (disk)
 			 2 rows processed

Listing 3: Index-Erzeugung und Ausführung einer Beispiel-Abfrage auf beide Spalten per Gleichheitsvergleich mittels SQL*Plus AUTOTRACE

anderen Statistiken über den untersuch-           welche führenden Werte sich für die                  In unserem konkreten Fall ist die Lage
ten Index auch die Spalte OPT_COMPR_              Kompression eignen können – ist also              allerdings auch ohne das Ausführen des
COUNT, welche die von Oracle ermittelte           in der Lage, das Verfahren flexibel an-           ANALYZE INDEX VALIDATE STRUCTURE-
optimale Anzahl von führenden Spalten für         zupassen und damit unterschiedliche               Befehls relativ offensichtlich – nur die
die „Basic Index Compression“ darstellt.          Einstellungen pro Block vornehmen zu              führende CODE-Spalte wiederholt sich si-
   Leider wird diese Information nur im           können, je nachdem, wie die tatsäch-              gnifikant, die weiteren ID- und ID2-Spal-
OFFLINE-Modus von VALIDATE STRUC-                 lichen Daten aussehen. Dies erlaubt               ten haben viele Ausprägungen und wie-
TURE ermittelt – und dieser wiederum              dann auch, den Kompressionseffekt                 derholen sich wenig, wenn auch pro Wert
sperrt den Index gegen Veränderungen              über die gesamte Index-Struktur gese-             zwei Mal. Insofern bekommen wir wahr-
während der Ausführung und ist somit              hen zu maximieren.                                scheinlich mit entweder nur der führen-
auf aktiven Datenbanken nicht wirklich                Inzwischen, seit der Version 12.2, gibt       den Spalte oder allen dreien den besten
sinnvoll einsetzbar – wohl dem, der eine          es auch noch eine „Advanced High In-              Kompressionseffekt – hier in Listing 1 ein
Kopie der Datenbank vorliegen hat, wo             dex Compression“, die ein ganz anderes            Beispiel dafür, was passiert, wenn ich nur
der Befehl ohne Auswirkungen ausge-               Konzept verfolgt und mit einem tatsäch-           die führende Spalte angebe.
führt werden kann.                                lichen Kompressionsverfahren nochmal                 Der Index ist jetzt ca. 10% kleiner ge-
   Die „Advanced Index Compression“               ganz andere Kompressionsfaktoren für              worden – mehr ist durch die Kürze der
löst dieses Problem auf sehr elegante             B*Tree-Indizes erreichen kann, wenn               CODE-Spalte nicht drin, allerdings spa-
Art und Weise – hier evaluiert Oracle             auch mit deutlich mehr CPU-Overhead               ren wir uns ohne weitere Kosten dadurch
für jeden Index Leaf Block automatisch,           verbunden.                                        eben 10% an Platz und bei entsprechen-

                                                                                                              Red Stack Magazin 01/2021          31
Datenbank

      select * from conc_index1 a where id between 0 and 250000 and id2 = 10001;

      ---------------------------------------------------------------------------------
      | Id | Operation          | Name        | Rows | Bytes | Cost (%CPU)| Time      |
      ---------------------------------------------------------------------------------
      |   0 | SELECT STATEMENT |              |     1 |    19 |   912   (3)| 00:00:01 |
      |* 1 | TABLE ACCESS FULL | CONC_INDEX1 |      1 |    19 |   912   (3)| 00:00:01 |
      ---------------------------------------------------------------------------------

      Predicate Information (identified by operation id):
      ---------------------------------------------------

          1 - filter("ID2"=10001 AND "ID"=0)

      Statistics
      ----------------------------------------------------------
      			 0 recursive calls
      			 0 db block gets
      			3289 consistent gets
      			 0 physical reads
      			 0 redo size
      			 555 bytes sent via SQL*Net to client
      			 371 bytes received via SQL*Net from client
      			 2 SQL*Net roundtrips to/from client
      			 0 sorts (memory)
      			 0 sorts (disk)
      			 2 rows processed

     Listing 4: Ergebnis bei Bereichssuche auf der ID-Spalte über die Hälfte des vorhandenen Werteraums

      select /*+ index(a) */ * from conc_index1 a where id between 0 and 250000 and id2 = 10001;

      -----------------------------------------------------------------------------------------------
      | Id | Operation                            | Name               | Rows | Bytes | Cost (%CPU)|
      -----------------------------------------------------------------------------------------------
      |   0 | SELECT STATEMENT                    |                    |     1 |    19 | 1472    (1)|
      |   1 | TABLE ACCESS BY INDEX ROWID BATCHED| CONC_INDEX1         |     1 |    19 | 1472    (1)|
      |* 2 |    INDEX RANGE SCAN                  | CONC_INDEX1_ID_ID2 |     1 |       | 1471    (1)|
      -----------------------------------------------------------------------------------------------

      Predicate Information (identified by operation id):
      ---------------------------------------------------
        2 - access("ID">=0 AND "ID2"=10001 AND "ID"
den Operationen auch 10% an Arbeit – in               SKIP SCAN-Zugriffen Abfragen verar-        denen Werteraums (Werteraum insgesamt
Listing 2 dargestellt am Beispiel des nicht           beiten kann.                               von 0 bis 499.999, Suche von 0 bis 250.000),
mehr so effizienten Zugriffs auf die erste        •   Das Voranstellen dieser Spalten mit we-    kann dies passieren (siehe Listing 4).
und dritte Spalte.                                    nig Ausprägungen erlaubt zusätzlich            Hier wird nicht mehr der Index vom
    Die Operation benötigt jetzt also auch            noch die bessere Nutzung der „Index        Optimizer herangezogen, sondern statt-
10% weniger logisches I/O, da die Index-              Compression“, die bei der „Enterprise      dessen ein „Full Table Scan“ durchgeführt
Struktur durch die Kompression kompak-                Edition“ von Oracle als „Basic Index       – eine ineffiziente Operation, um zwei Zei-
ter geworden ist.                                     Compression“ kostenfrei enthalten ist      len aus einer Million zu identifizieren. Er-
    Grundsätzlich kann man sich also fol-             und sowohl den Platzbedarf verringern      zwingen wir den Index-Zugriff per Hint,
gende Hinweise für die Reihenfolge von                als auch die Effizienz steigern kann.      wird auch klar, warum – wie sich in Listing
Spalten in B*Tree-Indizes merken:                                                                5 ablesen lässt.
                                                                                                     Durch Abfrage auf die Hälfte des Wer-
•   Solange man alle Spalten des Index bei        Mehrspalten-Indizes und                        teraums der führenden ID-Spalte kann
    der Suche angibt, spielt die Reihenfol-       Bereichsvergleiche                             nicht mehr effizient auf die ID2-Spalte
    ge der Spalten für die Effizienz des Zu-                                                     innerhalb der Index-Struktur zugegriffen
    griffs keine Rolle.                           Abschließend noch ein Beispiel dafür,          werden. Grund dafür ist der gleiche wie
•   Soll der Index auch für andere Zugriffe       was passiert, wenn mit Bereichsverglei-        oben; er ergibt sich aus der Vorsortierung
    verwendet werden können, sollten die          chen gearbeitet wird. Dazu erzeuge ich         der Werte in der Index-Struktur – effek-
    Spalten an den Anfang gestellt werden,        einen neuen Index auf der Tabelle, dies-       tiv wird also der halbe Index durchsucht,
    die den effizientesten Zugriff erlauben –     mal nur auf den Spalten ID und ID2. So-        darum sind die berechneten Kosten auch
    bei denen also die Index-Struktur am ef-      lange ich mit einfachen Werten suche,          höher als beim „Full Table Scan“ und die
    fizientesten navigiert werden kann ohne       funktioniert der Index-Zugriff effizient –     logischen I/Os entsprechen ungefähr
    Überspringen von Spalten oder Bereichs-       hier wie in Listing 3 dargestellt die Index-   der Hälfte der Index-Größe (2.919 Leaf
    abfragen (dazu gleich noch mehr).             Erzeugung sowie die Ausführung einer           Blocks, 1.467 logische I/Os). Oracle zeigt
•   Die Einsatzmöglichkeiten eines Index          Beispiel-Abfrage auf beide Spalten per         das auch wieder in der „Predicate Infor-
    können möglicherweise erhöht wer-             Gleichheitsvergleich mittels SQL*Plus          mation“ an – die Suchbedingung auf ID2
    den, wenn man Spalten mit wenig Aus-          AUTOTRACE.                                     wird als FILTER aufgeführt.
    prägungen an den Anfang des Index                Mache ich aber eine Bereichssuche auf           Möchte ich also die gezeigte Abfrage ef-
    stellt, da Oracle dann mittels INDEX          der ID-Spalte über die Hälfte des vorhan-      fizienter gestalten, muss ich die Reihenfol-

    CREATE INDEX conc_index1_id2_id ON conc_index1
    (id2, id);

    SQL> select * from conc_index1 a where id = 10000 and id2 = 10001;

    ----------------------------------------------------------------------------------------------------------
    | Id | Operation                            | Name               | Rows | Bytes | Cost (%CPU)| Time      |
    ----------------------------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT                    |                    |     2 |    38 |     5   (0)| 00:00:01 |
    |   1 | TABLE ACCESS BY INDEX ROWID BATCHED| CONC_INDEX1         |     2 |    38 |     5   (0)| 00:00:01 |
    |* 2 |    INDEX RANGE SCAN                  | CONC_INDEX1_ID2_ID |     2 |       |     3   (0)| 00:00:01 |
    ----------------------------------------------------------------------------------------------------------

    Predicate Information (identified by operation id):
    ---------------------------------------------------

      2 - access("ID2"=10001 AND "ID"=10000)

    Statistics
    ----------------------------------------------------------
    			 0 recursive calls
    			 0 db block gets
    			 6 consistent gets
    			 0 physical reads
    			 0 redo size
    			563 bytes sent via SQL*Net to client
    			372 bytes received via SQL*Net from client
    			 2 SQL*Net roundtrips to/from client
    			 0 sorts (memory)
    			 0 sorts (disk)
    			 2 rows processed

Listing 6: Abfrage mit ID2 als führender Spalte

                                                                                                          Red Stack Magazin 01/2021             33
Datenbank

      select * from conc_index1 where id between 0 and 250000 and id2 = 10001;

      ----------------------------------------------------------------------------------------------------------
      | Id | Operation                            | Name               | Rows | Bytes | Cost (%CPU)| Time      |
      ----------------------------------------------------------------------------------------------------------
      |   0 | SELECT STATEMENT                    |                    |     1 |    19 |     4   (0)| 00:00:01 |
      |   1 | TABLE ACCESS BY INDEX ROWID BATCHED| CONC_INDEX1         |     1 |    19 |     4   (0)| 00:00:01 |
      |* 2 |    INDEX RANGE SCAN                  | CONC_INDEX1_ID2_ID |     1 |       |     3   (0)| 00:00:01 |
      ----------------------------------------------------------------------------------------------------------

      Predicate Information (identified by operation id):
      ---------------------------------------------------

         2 - access("ID2"=10001 AND "ID">=0 AND "ID"
Sie können auch lesen