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