Zusammengesetzte Indizes: Selektivste Spalte zuerst?

17

Ich habe darüber gelesen composite indexesund bin etwas verwirrt über die Bestellung. Diese Dokumentation (etwas weniger als die Hälfte) sagt

Im Allgemeinen sollten Sie die Spalte, von der erwartet wird, dass sie am häufigsten verwendet wird, zuerst in den Index aufnehmen.

Doch kurz danach heißt es

Erstellen Sie einen zusammengesetzten Index, in dem die selektivste Spalte an erster Stelle steht. das ist die Spalte mit den meisten Werten.

Oracle sagt es auch hier mit anderen Worten

Wenn alle Schlüssel in WHERE-Klauseln gleich häufig verwendet werden, wird die Abfrageleistung am besten verbessert, wenn diese Schlüssel in der Anweisung CREATE INDEX von am selektivsten zu am wenigsten selektiv sortiert werden.

Ich habe jedoch eine SO-Antwort gefunden , die anders aussagt. Es sagt

Ordnen Sie die Spalten mit der am wenigsten ausgewählten Spalte zuerst und der am meisten ausgewählten Spalte zuletzt an. Im Falle eines Unentschieden führen Sie mit der Säule, die eher für sich allein verwendet wird.

Die erste Dokumentation, auf die ich verwiesen habe, besagt, dass Sie zuerst die am häufigsten verwendete verwenden sollten, während die SO-Antwort besagt, dass dies nur zum Brechen von Krawatten sein sollte. Dann unterscheiden sie sich auch in der Bestellung.

Diese Dokumentation spricht auch über skip scanningund sagt

Das Überspringen von Scans ist vorteilhaft, wenn in der ersten Spalte des zusammengesetzten Index nur wenige unterschiedliche Werte und im nicht führenden Schlüssel des Index viele unterschiedliche Werte vorhanden sind.

Ein anderer Artikel sagt

Die Präfixspalte sollte die unterscheidendste und in Abfragen am häufigsten verwendete sein

Was meiner Meinung nach am diskriminierendsten ist, würde am unterscheidendsten bedeuten.

All diese Nachforschungen führen mich immer noch zu derselben Frage. sollte die selektivste Spalte zuerst oder zuletzt sein? Sollte die erste Spalte bei einem Gleichstand die am häufigsten verwendete und nur die selektivste sein?

Diese Artikel scheinen sich zu widersprechen, aber sie bieten einige Beispiele. Nach allem, was ich gesammelt habe, scheint es effizienter least selective columnzu sein, als Erster in der Bestellung zu sein, wenn Sie damit rechnen Index Skip Scans. Aber ich bin mir nicht sicher, ob das richtig ist.

Eric
quelle

Antworten:

8

Von AskTom

(In 9i gibt es einen neuen "Index-Sprung-Scan" - suchen Sie danach, um darüber zu lesen. Dies macht den Index (a, b) ODER (b, a) manchmal in beiden oben genannten Fällen nützlich!)

Die Reihenfolge der Spalten in Ihrem Index hängt also davon ab, wie Ihre Abfragen geschrieben werden. Sie möchten in der Lage sein, den Index für so viele Abfragen wie möglich zu verwenden (um die Gesamtzahl der vorhandenen Indizes zu verringern) - dies bestimmt die Reihenfolge der Spalten. Sonst nichts (Selektivität von a oder b zählt überhaupt nicht).

Eines der Argumente für das Anordnen von Spalten im zusammengesetzten Index in der Reihenfolge von den am wenigsten unterscheidenden (weniger unterschiedlichen Werten) bis zu den am meisten unterscheidenden (mehr unterschiedlichen Werten) ist die Komprimierung von Indexschlüsseln.

SQL> create table t as select * from all_objects;

Table created.

SQL> create index t_idx_1 on t(owner,object_type,object_name);

Index created.

SQL> create index t_idx_2 on t(object_name,object_type,owner);

Index created.

SQL> select count(distinct owner), count(distinct object_type), count(distinct object_name ), count(*)  from t;

COUNT(DISTINCTOWNER) COUNT(DISTINCTOBJECT_TYPE) COUNT(DISTINCTOBJECT_NAME)      COUNT(*)
-------------------- -------------------------- --------------------------      ----------
                 30                         45                       52205      89807

SQL> analyze index t_idx_1 validate structure; 

Index analyzed.

SQL> select btree_space, pct_used, opt_cmpr_count, opt_cmpr_pctsave from index_stats;

BTREE_SPACE   PCT_USED OPT_CMPR_COUNT OPT_CMPR_PCTSAVE
----------- ---------- -------------- ----------------
    5085584     90          2           28

SQL> analyze index t_idx_2 validate structure; 

Index analyzed.

SQL> select btree_space, pct_used, opt_cmpr_count, opt_cmpr_pctsave  from index_stats; 

BTREE_SPACE   PCT_USED OPT_CMPR_COUNT OPT_CMPR_PCTSAVE
----------- ---------- -------------- ----------------
    5085584     90          1           14

Laut Indexstatistik ist der erste Index komprimierbarer.

Zum anderen wird der Index in Ihren Abfragen verwendet. Wenn Ihre Abfragen meistens verwenden col1,

Wenn Sie beispielsweise Fragen haben,

  • select * from t where col1 = :a and col2 = :b;
  • select * from t where col1 = :a;

    -dann index(col1,col2)würde besser abschneiden .

    Wenn Ihre Abfragen meistens verwenden col2,

  • select * from t where col1 = :a and col2 = :b;
  • select * from t where col2 = :b;

    -dann index(col2,col1)würde besser abschneiden . Wenn alle Ihre Abfragen immer beide Spalten angeben, spielt es keine Rolle, welche Spalte im zusammengesetzten Index an erster Stelle steht.

    Zusammenfassend lässt sich sagen, dass die wichtigsten Überlegungen bei der Spaltenreihenfolge des zusammengesetzten Index die Komprimierung des Indexschlüssels sind und wie Sie diesen Index in Ihren Abfragen verwenden werden.

    Verweise:

  • Spaltenreihenfolge im Index
  • Es ist weniger effizient, führende Spalten mit geringer Kardinalität in einem Index zu haben (richtig)?
  • Index Skip Scan - spielt die Reihenfolge der Indexspalten noch eine Rolle? (Warnschild)

  • JSapkota
    quelle
    3

    Das selektivste zuerst ist nur dann sinnvoll, wenn sich diese Spalte in der tatsächlichen WHERE-Klausel befindet.

    Wenn SELECT von einer größeren Gruppe (weniger selektiv) und möglicherweise von anderen, nicht indizierten Werten stammt, kann ein Index mit weniger selektiven Spalten dennoch nützlich sein (wenn es einen Grund gibt, keinen weiteren zu erstellen).

    Wenn es eine Tischadresse gibt, mit

    COUNTRY CITY STREET, noch etwas ...

    Indizieren von STREET, CITY, COUNTRY liefert die schnellsten Abfragen mit einem Straßennamen. Wenn jedoch alle Straßen einer Stadt abgefragt werden, ist der Index unbrauchbar und die Abfrage führt wahrscheinlich einen vollständigen Tabellenscan durch.

    Die Indizierung von COUNTRY, CITY, STREET kann für einzelne Straßen etwas langsamer sein, aber der Index kann für andere Abfragen verwendet werden, wobei nur nach Land und / oder Stadt ausgewählt wird.

    Erik Hart
    quelle
    3

    Bei der Auswahl der Reihenfolge der Indexspalten gilt Folgendes:

    Gibt es (Gleichheits-) Prädikate für diese Spalte in meinen Abfragen?

    Wenn eine Spalte in einer where-Klausel niemals vorkommt, lohnt es sich nicht, sie zu indizieren (1)

    OK, Sie haben also eine Tabelle und Abfragen für jede Spalte. Manchmal mehr als eins.

    Wie entscheiden Sie, was Sie indizieren möchten?

    Schauen wir uns ein Beispiel an. Hier ist eine Tabelle mit drei Spalten. Einer enthält 10 Werte, weitere 1.000, die letzten 10.000:

    create table t(
      few_vals  varchar2(10),
      many_vals varchar2(10),
      lots_vals varchar2(10)
    );
    
    insert into t 
    with rws as (
      select lpad(mod(rownum, 10), 10, '0'), 
             lpad(mod(rownum, 1000), 10, '0'), 
             lpad(rownum, 10, '0') 
      from dual connect by level <= 10000
    )
      select * from rws;
    
    commit;
    
    select count(distinct few_vals),
           count(distinct many_vals) ,
           count(distinct lots_vals) 
    from   t;
    
    COUNT(DISTINCTFEW_VALS)  COUNT(DISTINCTMANY_VALS)  COUNT(DISTINCTLOTS_VALS)  
    10                       1,000                     10,000     
    

    Dies sind Zahlen, die mit Nullen aufgefüllt bleiben. Auf diese Weise können Sie später auf die Komprimierung eingehen.

    Sie haben also drei häufig gestellte Fragen:

    select count (distinct few_vals || ':' || many_vals || ':' || lots_vals )
    from   t
    where  few_vals = '0000000001';
    
    select count (distinct few_vals || ':' || many_vals || ':' || lots_vals )
    from   t
    where  lots_vals = '0000000001';
    
    select count (distinct few_vals || ':' || many_vals || ':' || lots_vals )
    from   t
    where  lots_vals = '0000000001'
    and    few_vals = '0000000001';

    Was indexierst du?

    Ein Index für nur wenige Werte ist nur unwesentlich besser als ein vollständiger Tabellenscan:

    select count (distinct few_vals || ':' || many_vals || ':' || lots_vals )
    from   t
    where  few_vals = '0000000001';
    
    select * 
    from table(dbms_xplan.display_cursor(null, null, 'IOSTATS LAST -PREDICATE'));
    
    -------------------------------------------------------------------------------------------  
    | Id  | Operation            | Name     | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  
    -------------------------------------------------------------------------------------------  
    |   0 | SELECT STATEMENT     |          |      1 |        |      1 |00:00:00.01 |      61 |  
    |   1 |  SORT AGGREGATE      |          |      1 |      1 |      1 |00:00:00.01 |      61 |  
    |   2 |   VIEW               | VW_DAG_0 |      1 |   1000 |   1000 |00:00:00.01 |      61 |  
    |   3 |    HASH GROUP BY     |          |      1 |   1000 |   1000 |00:00:00.01 |      61 |  
    |   4 |     TABLE ACCESS FULL| T        |      1 |   1000 |   1000 |00:00:00.01 |      61 |  
    -------------------------------------------------------------------------------------------
    
    select /*+ index (t (few_vals)) */
           count (distinct few_vals || ':' || many_vals || ':' || lots_vals )
    from   t
    where  few_vals = '0000000001';
    
    select * 
    from   table(dbms_xplan.display_cursor(null, null, 'IOSTATS LAST -PREDICATE'));
    
    -------------------------------------------------------------------------------------------------------------  
    | Id  | Operation                              | Name     | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  
    -------------------------------------------------------------------------------------------------------------  
    |   0 | SELECT STATEMENT                       |          |      1 |        |      1 |00:00:00.01 |      58 |  
    |   1 |  SORT AGGREGATE                        |          |      1 |      1 |      1 |00:00:00.01 |      58 |  
    |   2 |   VIEW                                 | VW_DAG_0 |      1 |   1000 |   1000 |00:00:00.01 |      58 |  
    |   3 |    HASH GROUP BY                       |          |      1 |   1000 |   1000 |00:00:00.01 |      58 |  
    |   4 |     TABLE ACCESS BY INDEX ROWID BATCHED| T        |      1 |   1000 |   1000 |00:00:00.01 |      58 |  
    |   5 |      INDEX RANGE SCAN                  | FEW      |      1 |   1000 |   1000 |00:00:00.01 |       5 |  
    -------------------------------------------------------------------------------------------------------------

    Es ist also unwahrscheinlich, dass es sich lohnt, eine eigene Indexierung vorzunehmen. Abfragen auf lots_vals geben nur wenige Zeilen zurück (in diesem Fall nur 1). Das ist also definitiv eine Indizierung wert.

    Aber was ist mit den Abfragen für beide Spalten?

    Sollten Sie indizieren:

    ( few_vals, lots_vals )

    ODER

    ( lots_vals, few_vals )

    Fangfrage!

    Die Antwort ist weder.

    Sicher, few_vals ist eine lange Zeichenfolge. Sie können also eine gute Komprimierung erzielen. Und Sie erhalten (möglicherweise) einen Index-Skip-Scan für die Abfragen mit (few_vals, lots_vals), die nur Prädikate für lots_vals enthalten. Aber ich bin nicht hier, obwohl es deutlich besser abschneidet als ein vollständiger Scan:

    create index few_lots on t(few_vals, lots_vals);
    
    select count (distinct few_vals || ':' || many_vals || ':' || lots_vals )
    from   t
    where  lots_vals = '0000000001';
    
    select * 
    from   table(dbms_xplan.display_cursor(null, null, 'IOSTATS LAST -PREDICATE'));
    
    -------------------------------------------------------------------------------------------  
    | Id  | Operation            | Name     | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  
    -------------------------------------------------------------------------------------------  
    |   0 | SELECT STATEMENT     |          |      1 |        |      1 |00:00:00.01 |      61 |  
    |   1 |  SORT AGGREGATE      |          |      1 |      1 |      1 |00:00:00.01 |      61 |  
    |   2 |   VIEW               | VW_DAG_0 |      1 |      1 |      1 |00:00:00.01 |      61 |  
    |   3 |    HASH GROUP BY     |          |      1 |      1 |      1 |00:00:00.01 |      61 |  
    |   4 |     TABLE ACCESS FULL| T        |      1 |      1 |      1 |00:00:00.01 |      61 |  
    -------------------------------------------------------------------------------------------  
    
    select /*+ index_ss (t few_lots) */count (distinct few_vals || ':' || many_vals || ':' || lots_vals )
    from   t
    where  lots_vals = '0000000001';
    
    select * 
    from   table(dbms_xplan.display_cursor(null, null, 'IOSTATS LAST -PREDICATE'));
    
    ----------------------------------------------------------------------------------------------------------------------  
    | Id  | Operation                              | Name     | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  |  
    ----------------------------------------------------------------------------------------------------------------------  
    |   0 | SELECT STATEMENT                       |          |      1 |        |      1 |00:00:00.01 |      13 |     11 |  
    |   1 |  SORT AGGREGATE                        |          |      1 |      1 |      1 |00:00:00.01 |      13 |     11 |  
    |   2 |   VIEW                                 | VW_DAG_0 |      1 |      1 |      1 |00:00:00.01 |      13 |     11 |  
    |   3 |    HASH GROUP BY                       |          |      1 |      1 |      1 |00:00:00.01 |      13 |     11 |  
    |   4 |     TABLE ACCESS BY INDEX ROWID BATCHED| T        |      1 |      1 |      1 |00:00:00.01 |      13 |     11 |  
    |   5 |      INDEX SKIP SCAN                   | FEW_LOTS |      1 |     40 |      1 |00:00:00.01 |      12 |     11 |  
    ----------------------------------------------------------------------------------------------------------------------

    Spielen Sie gerne? (2)

    Sie benötigen also weiterhin einen Index mit lots_vals als führende Spalte. Und zumindest in diesem Fall erledigt der zusammengesetzte Index (wenige, Lose) die gleiche Menge an Arbeit wie einer an nur (Losen).

    select count (distinct few_vals || ':' || many_vals || ':' || lots_vals )
    from   t
    where  lots_vals = '0000000001'
    and    few_vals = '0000000001';
    
    select * 
    from   table(dbms_xplan.display_cursor(null, null, 'IOSTATS LAST -PREDICATE'));
    
    -------------------------------------------------------------------------------------------------------------  
    | Id  | Operation                              | Name     | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  
    -------------------------------------------------------------------------------------------------------------  
    |   0 | SELECT STATEMENT                       |          |      1 |        |      1 |00:00:00.01 |       3 |  
    |   1 |  SORT AGGREGATE                        |          |      1 |      1 |      1 |00:00:00.01 |       3 |  
    |   2 |   VIEW                                 | VW_DAG_0 |      1 |      1 |      1 |00:00:00.01 |       3 |  
    |   3 |    HASH GROUP BY                       |          |      1 |      1 |      1 |00:00:00.01 |       3 |  
    |   4 |     TABLE ACCESS BY INDEX ROWID BATCHED| T        |      1 |      1 |      1 |00:00:00.01 |       3 |  
    |   5 |      INDEX RANGE SCAN                  | FEW_LOTS |      1 |      1 |      1 |00:00:00.01 |       2 |  
    -------------------------------------------------------------------------------------------------------------  
    
    create index lots on t(lots_vals);
    
    select /*+ index (t (lots_vals)) */count (distinct few_vals || ':' || many_vals || ':' || lots_vals )
    from   t
    where  lots_vals = '0000000001'
    and    few_vals = '0000000001';
    
    select * 
    from   table(dbms_xplan.display_cursor(null, null, 'IOSTATS LAST -PREDICATE'));
    
    ----------------------------------------------------------------------------------------------------------------------  
    | Id  | Operation                              | Name     | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  |  
    ----------------------------------------------------------------------------------------------------------------------  
    |   0 | SELECT STATEMENT                       |          |      1 |        |      1 |00:00:00.01 |       3 |      1 |  
    |   1 |  SORT AGGREGATE                        |          |      1 |      1 |      1 |00:00:00.01 |       3 |      1 |  
    |   2 |   VIEW                                 | VW_DAG_0 |      1 |      1 |      1 |00:00:00.01 |       3 |      1 |  
    |   3 |    HASH GROUP BY                       |          |      1 |      1 |      1 |00:00:00.01 |       3 |      1 |  
    |   4 |     TABLE ACCESS BY INDEX ROWID BATCHED| T        |      1 |      1 |      1 |00:00:00.01 |       3 |      1 |  
    |   5 |      INDEX RANGE SCAN                  | LOTS     |      1 |      1 |      1 |00:00:00.01 |       2 |      1 |  
    ----------------------------------------------------------------------------------------------------------------------  

    In einigen Fällen werden Sie durch den zusammengesetzten Index 1-2 E / A-Vorgänge sparen. Aber lohnt es sich, zwei Indizes für diese Einsparung zu haben?

    Und es gibt ein weiteres Problem mit dem zusammengesetzten Index. Vergleichen Sie den Clustering-Faktor für die drei Indizes, einschließlich LOTS_VALS:

    create index lots on t(lots_vals);
    create index lots_few on t(lots_vals, few_vals);
    create index few_lots on t(few_vals, lots_vals);
    
    select index_name, leaf_blocks, distinct_keys, clustering_factor
    from   user_indexes
    where  table_name = 'T';
    
    INDEX_NAME  LEAF_BLOCKS  DISTINCT_KEYS  CLUSTERING_FACTOR  
    FEW_LOTS    47           10,000         530                
    LOTS_FEW    47           10,000         53                 
    LOTS        31           10,000         53                 
    FEW         31           10             530    

    Beachten Sie, dass der Clustering-Faktor für wenige_Lose 10x höher ist als für Lose und Lose_Wenige! Und das ist in einer Demo-Tabelle mit perfektem Clustering zu Beginn. In Datenbanken der realen Welt ist der Effekt wahrscheinlich schlechter.

    Was ist daran so schlimm?

    Der Clustering-Faktor ist einer der Schlüsseltreiber für die Attraktivität eines Index. Je höher dieser Wert ist, desto unwahrscheinlicher ist es, dass der Optimierer ihn auswählt. Insbesondere, wenn lots_vals nicht eindeutig sind, aber normalerweise nur wenige Zeilen pro Wert enthalten. Wenn Sie Pech haben, könnte dies ausreichen, um den Optimierer zu veranlassen, einen vollständigen Scan für billiger zu halten ...

    OK, also haben zusammengesetzte Indizes mit few_vals und lots_vals nur Vorteile in Bezug auf Groß- und Kleinschreibung.

    Was ist mit Abfragen, die wenige_Werte und viele_Werte filtern?

    Einzelne Spaltenindizes bieten nur geringe Vorteile. Zusammen liefern sie jedoch nur wenige Werte. Ein zusammengesetzter Index ist also eine gute Idee. Aber in welche Richtung?

    Wenn Sie einige zuerst platzieren, wird diese durch Komprimieren der führenden Spalte kleiner

    create index few_many on t(many_vals, few_vals);
    create index many_few on t(few_vals, many_vals);
    
    select index_name, leaf_blocks, distinct_keys, clustering_factor 
    from   user_indexes
    where  index_name in ('FEW_MANY', 'MANY_FEW');
    
    INDEX_NAME  LEAF_BLOCKS  DISTINCT_KEYS  CLUSTERING_FACTOR  
    FEW_MANY    47           1,000          10,000             
    MANY_FEW    47           1,000          10,000   
    
    alter index few_many rebuild compress 1;
    alter index many_few rebuild compress 1;
    
    select index_name, leaf_blocks, distinct_keys, clustering_factor 
    from   user_indexes
    where  index_name in ('FEW_MANY', 'MANY_FEW');
    
    INDEX_NAME  LEAF_BLOCKS  DISTINCT_KEYS  CLUSTERING_FACTOR  
    MANY_FEW    31           1,000          10,000             
    FEW_MANY    34           1,000          10,000      

    Mit weniger unterschiedlichen Werten in der führenden Spalte wird besser komprimiert. Es ist also etwas weniger Arbeit, diesen Index zu lesen. Aber nur geringfügig. Und beide sind bereits ein gutes Stück kleiner als das Original (25% Größenreduzierung).

    Und Sie können noch weiter gehen und den gesamten Index komprimieren!

    alter index few_many rebuild compress 2;
    alter index many_few rebuild compress 2;
    
    select index_name, leaf_blocks, distinct_keys, clustering_factor 
    from   user_indexes
    where  index_name in ('FEW_MANY', 'MANY_FEW');
    
    INDEX_NAME  LEAF_BLOCKS  DISTINCT_KEYS  CLUSTERING_FACTOR  
    FEW_MANY    20           1,000          10,000             
    MANY_FEW    20           1,000          10,000   

    Jetzt haben beide Indizes wieder dieselbe Größe. Beachten Sie, dass dies die Tatsache ausnutzt, dass es eine Beziehung zwischen wenigen und vielen gibt. Wieder ist es unwahrscheinlich, dass Sie diese Art von Nutzen in der realen Welt sehen werden.

    Bisher haben wir nur über Gleichstellungsprüfungen gesprochen. Bei zusammengesetzten Indizes tritt häufig eine Ungleichung mit einer der Spalten auf. zB Abfragen wie "Aufträge / Lieferungen / Rechnungen für einen Kunden in den letzten N Tagen abrufen".

    Wenn Sie diese Art von Abfragen haben, möchten Sie die Gleichheit mit der ersten Spalte des Index:

    select count (distinct few_vals || ':' || many_vals || ':' || lots_vals )
    from   t
    where  few_vals < '0000000002'
    and    many_vals = '0000000001';
    
    select * 
    from   table(dbms_xplan.display_cursor(null, null, 'IOSTATS LAST -PREDICATE'));
    
    -------------------------------------------------------------------------------------------------------------  
    | Id  | Operation                              | Name     | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  
    -------------------------------------------------------------------------------------------------------------  
    |   0 | SELECT STATEMENT                       |          |      1 |        |      1 |00:00:00.01 |      12 |  
    |   1 |  SORT AGGREGATE                        |          |      1 |      1 |      1 |00:00:00.01 |      12 |  
    |   2 |   VIEW                                 | VW_DAG_0 |      1 |     10 |     10 |00:00:00.01 |      12 |  
    |   3 |    HASH GROUP BY                       |          |      1 |     10 |     10 |00:00:00.01 |      12 |  
    |   4 |     TABLE ACCESS BY INDEX ROWID BATCHED| T        |      1 |     10 |     10 |00:00:00.01 |      12 |  
    |   5 |      INDEX RANGE SCAN                  | FEW_MANY |      1 |     10 |     10 |00:00:00.01 |       2 |  
    -------------------------------------------------------------------------------------------------------------  
    
    select count (distinct few_vals || ':' || many_vals || ':' || lots_vals )
    from   t
    where  few_vals = '0000000001'
    and    many_vals < '0000000002';
    
    select * 
    from   table(dbms_xplan.display_cursor(null, null, 'IOSTATS LAST -PREDICATE'));
    
    ----------------------------------------------------------------------------------------------------------------------  
    | Id  | Operation                              | Name     | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  |  
    ----------------------------------------------------------------------------------------------------------------------  
    |   0 | SELECT STATEMENT                       |          |      1 |        |      1 |00:00:00.01 |      12 |      1 |  
    |   1 |  SORT AGGREGATE                        |          |      1 |      1 |      1 |00:00:00.01 |      12 |      1 |  
    |   2 |   VIEW                                 | VW_DAG_0 |      1 |      2 |     10 |00:00:00.01 |      12 |      1 |  
    |   3 |    HASH GROUP BY                       |          |      1 |      2 |     10 |00:00:00.01 |      12 |      1 |  
    |   4 |     TABLE ACCESS BY INDEX ROWID BATCHED| T        |      1 |      2 |     10 |00:00:00.01 |      12 |      1 |  
    |   5 |      INDEX RANGE SCAN                  | MANY_FEW |      1 |      1 |     10 |00:00:00.01 |       2 |      1 |  
    ----------------------------------------------------------------------------------------------------------------------  

    Beachten Sie, dass sie den entgegengesetzten Index verwenden.

    TL; DR

    • Spalten mit Gleichheitsbedingungen sollten im Index an erster Stelle stehen.
    • Wenn Ihre Abfrage mehrere Spalten mit Gleichheiten enthält, erzielen Sie den besten Komprimierungsvorteil, wenn Sie die Spalten mit den wenigsten unterschiedlichen Werten zuerst platzieren
    • Auch wenn Index-Skip-Scans möglich sind, müssen Sie sicher sein, dass dies auf absehbare Zeit eine gangbare Option bleibt
    • Zusammengesetzte Indizes mit nahezu eindeutigen Spalten bieten nur minimale Vorteile. Stellen Sie sicher, dass Sie die 1-2 E / As wirklich speichern müssen

    1: In einigen Fällen kann es sinnvoll sein, eine Spalte in einen Index aufzunehmen, wenn dies bedeutet, dass sich alle Spalten in Ihrer Abfrage im Index befinden. Dadurch wird nur ein Index-Scan aktiviert, sodass Sie nicht auf die Tabelle zugreifen müssen.

    2: Wenn Sie für Diagnostics and Tuning lizenziert sind, können Sie den Plan mit SQL Plan Management zu einem Skip-Scan zwingen

    ADDEDNDA

    PS - die Dokumente, die Sie dort zitiert haben, stammen von 9i. Das ist wirklich alt. Ich würde Stick mit etwas aktuellere

    Chris Saxon
    quelle
    Ist eine Abfrage mit select count (distinct few_vals || ':' || many_vals || ':' || lots_vals )wirklich üblich? Lässt Oracle die Syntax nicht zu select count (distinct few_vals, many_vals, lots_vals )- die keine Verkettung von Zeichenfolgen vornimmt, die Spalten nicht als Texttypen benötigt und nicht auf die Abwesenheit von :Zeichen angewiesen ist ?
    ypercubeᵀᴹ
    @ ypercubeᵀᴹ count ( distinct x, y, z )In Oracle ist das nicht möglich. Sie müssen also eine bestimmte Unterabfrage durchführen und die Ergebnisse oder eine Verkettung wie oben zählen. Ich habe es gerade hier gemacht, um einen Tabellenzugriff zu erzwingen (anstatt nur einen Index zu scannen) und nur eine Zeile im Ergebnis zu haben
    Chris Saxon
    1

    Es gibt weitere Elemente der Abfrage, die zur endgültigen Entscheidung beitragen, womit ein zusammengesetzter Index beginnen und / oder neben der Selektivität der Spalte enthalten soll.

    beispielsweise:

    1. Welche Art von Abfrageoperator wird verwendet: Wenn Abfragen Operatoren wie
      ">,> =, <, <=" haben
    2. Wie viele tatsächliche Zeilen werden als Ergebnis der Abfrage erwartet: Enthält das Abfrageergebnis die meisten Zeilen aus der Tabelle?
    3. Werden während der Where-Klausel irgendwelche Funktionen für die Tabellenspalte verwendet? Wenn die Abfrage eine Funktion UPPER, LOWER, TRIM, SUBSTRING hat, die für die Spalte verwendet wird, die in der WHERE-Bedingung verwendet wird.

    Um die Konversation dennoch relevant zu halten, gilt die unten stehende Antwort für die folgende Situation:

    1. 90% der Abfragetypen für eine bestimmte Tabelle haben die WHERE-Klausel mit operator =
    2. "Höchstens die Abfrage gibt die 10% der gesamten Zeilen in der Tabelle als Ergebnis zurück."
    3. msgstr "In der WHERE - Klausel werden keine Funktionen für die Tabellenspalte verwendet."
    4. "Die meisten Zeitspalten in der WHERE-Klausel haben den Typ number,
      string"

    Meiner Erfahrung nach sollte DBA beides berücksichtigen.

    Stellen wir uns vor, die einzige Regel wird angewendet:

    1) Wenn ich einen Index erstelle, dessen selektivste Spalte jedoch von den meisten Abfragen in dieser Tabelle nicht verwendet wird, ist dies für die Datenbank-Engine nicht sinnvoll.

    2) Wenn ich einen Index mit der am häufigsten verwendeten Spalte in einer Abfrage erst im Index anlege, die Spalte jedoch eine geringe Selektivität aufweist, ist auch meine Abfrageleistung nicht gut.

    Ich werde Spalten auflisten, die in 90% der Tabellenabfragen am häufigsten verwendet werden. Dann setzen Sie diese nur in der Reihenfolge der meisten Kardinalität auf die geringste Kardinalität.

    Wir verwenden Indizes, um die Leistung von Leseabfragen zu verbessern, und dieser Workflow (Arten von Leseabfragen) sollte nur die Indexerstellung vorantreiben. Tatsächlich kann der komprimierte Index mit wachsender Datenmenge (Milliarden von Zeilen) zwar Speicherplatz sparen, die Leseabfrageleistung jedoch beeinträchtigen.

    Anup Shah
    quelle
    1

    Theoretisch ergibt die selektivste Spalte die schnellste Suche. Aber bei der Arbeit bin ich nur auf eine Situation gestoßen, in der wir einen zusammengesetzten Index von 3 Teilen haben, wobei der selektivste Teil zuerst steht. (Datum, Autor, Verlag sagen wir mal, in dieser Reihenfolge überwacht die Tabelle die Daumen nach oben bei Beiträgen) und ich habe eine Abfrage, die alle drei Teile verwendet. Mysql verwendet standardmäßig den Autorenindex, wobei der zusammengesetzte Index mit Firma und Datum übersprungen wird, obwohl er in meiner Abfrage vorhanden ist. Ich habe Force-Index verwendet, um das Composite zu verwenden, und die Abfrage wurde tatsächlich langsamer ausgeführt. Warum ist das passiert? Ich werde dir sagen:

    Ich habe einen Bereich am Datum ausgewählt, und obwohl das Datum sehr selektiv ist, hat die Tatsache, dass wir ihn für Bereichsscans verwenden (obwohl der Bereich relativ kurz ist, 6 Monate von 6 Jahren Daten), den Verbund für schädlich gemacht MySQL. Um das Composite in diesem speziellen Fall zu verwenden, muss mysql alle Artikel abrufen, die seit Neujahr geschrieben wurden, und sich dann damit befassen, wer der Autor ist. Da der Autor nicht so viele Artikel im Vergleich zu anderen Autoren geschrieben hat, hat mysql es vorgezogen, nur diesen Autor zu finden .

    In einem anderen Fall lief die Abfrage auf dem Composite sehr viel schneller. Damals war ein Autor äußerst beliebt und besaß die meisten Datensätze. Eine Sortierung nach Datum war sinnvoll. Aber MySQL hat diesen Fall nicht automatisch erkannt. Ich musste den Index erzwingen. Bereichsscans können Ihre selektive Spalte unbrauchbar machen. Die Verteilung der Daten kann zu Fällen führen, in denen Spalten für verschiedene Datensätze selektiver sind ...

    Was ich anders machen würde, ist, das Datum (was theoretisch am selektivsten ist) nach rechts zu verschieben, da ich weiß, dass ich jetzt einen Entfernungsscan durchführen werde, und das macht einen Unterschied.

    Joe Yahchouchi
    quelle
    1
    Wenn Ihre Abfrage so etwas hätte, wäre WHERE (date BETWEEN @x AND @y) AND (author = @a) AND (publishing company = @p)ein Index für (author, publishing_company, date)oder (publishing_company, author, date)für besser und würde verwendet werden - ohne ihn zu erzwingen.
    ypercubeᵀᴹ
    -2

    Unterschiedliche Fälle für unterschiedliche Situationen. Kennen Sie Ihr Ziel; Erstellen Sie dann Ihre Indizes und führen Sie Erklärungspläne für jeden aus, um die beste Antwort für Ihre Situation zu erhalten.

    RMPJ
    quelle
    -2

    Aus der Spaltenreihenfolge im Index auf Ask Tom:

    Die Reihenfolge der Spalten in Ihrem Index hängt also davon ab, wie Ihre Abfragen geschrieben werden. Sie möchten in der Lage sein, den Index für so viele Abfragen wie möglich zu verwenden (um die Gesamtzahl der vorhandenen Indizes zu verringern) - dies bestimmt die Reihenfolge der Spalten. Sonst nichts (Selektivität von a oder b zählt überhaupt nicht).

    Stimmen Sie zu, dass wir Spalten basierend auf der where-Klausel sortieren müssen, aber die Aussage "(Selektivität von a oder b zählt überhaupt nicht)" ist nicht korrekt ("where-Klausel")

    Andjelko Miovcic
    quelle