Saat memilih urutan kolom indeks, perhatian utama adalah:
Apakah ada (kesetaraan) predikat terhadap kolom ini di kueri saya?
Jika sebuah kolom tidak pernah muncul dalam klausa where, itu tidak layak diindeks (1)
OK, jadi Anda punya tabel dan kueri terhadap setiap kolom. Terkadang lebih dari satu.
Bagaimana Anda memutuskan apa yang akan diindeks?
Mari kita lihat sebuah contoh. Berikut adalah tabel dengan tiga kolom. Satu memiliki 10 nilai, 1.000 lainnya, 10.000 terakhir:
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
Ini adalah angka yang dibiarkan penuh dengan nol. Ini akan membantu menjelaskan tentang kompresi nanti.
Jadi, Anda memiliki tiga pertanyaan umum:
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';
Apa yang Anda indeks?
Indeks hanya pada few_vals hanya sedikit lebih baik daripada pemindaian tabel lengkap:
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 |
-------------------------------------------------------------------------------------------------------------
Jadi tidak mungkin untuk mengindeks sendiri. Kueri pada lots_vals mengembalikan beberapa baris (hanya 1 dalam kasus ini). Jadi ini pasti layak diindeks.
Tetapi bagaimana dengan pertanyaan terhadap kedua kolom?
Haruskah Anda mengindeks:
( few_vals, lots_vals )
ATAU
( lots_vals, few_vals )
Pertanyaan jebakan!
Jawabannya adalah tidak.
Tentu, few_vals adalah string yang panjang. Jadi Anda bisa mendapatkan kompresi yang baik dari itu. Dan Anda (mungkin) mendapatkan pemindaian lompatan indeks untuk kueri menggunakan (few_vals, lots_vals) yang hanya memiliki predikat pada lots_vals. Tapi saya tidak di sini, meskipun kinerjanya jauh lebih baik daripada pemindaian penuh:
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 |
----------------------------------------------------------------------------------------------------------------------
Apakah Anda suka judi? (2)
Jadi, Anda masih memerlukan indeks dengan lots_vals sebagai kolom utama. Dan setidaknya dalam hal ini indeks gabungan (beberapa, lot) melakukan jumlah pekerjaan yang sama dengan satu pada hanya (banyak)
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 |
----------------------------------------------------------------------------------------------------------------------
Akan ada kasus di mana indeks gabungan menghemat 1-2 IOs. Tetapi apakah layak memiliki dua indeks untuk penghematan ini?
Dan ada masalah lain dengan indeks komposit. Bandingkan faktor pengelompokan untuk tiga indeks termasuk 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
Perhatikan bahwa faktor pengelompokan untuk few_lots adalah 10x lebih tinggi daripada untuk banyak dan banyak_few! Dan ini ada di tabel demo dengan pengelompokan yang sempurna untuk memulai. Dalam database dunia nyata pengaruhnya mungkin lebih buruk.
Jadi apa yang buruk tentang itu?
Faktor pengelompokan adalah salah satu pendorong utama yang menentukan seberapa "menarik" suatu indeks. Semakin tinggi, semakin kecil kemungkinan pengoptimal untuk memilihnya. Terutama jika lots_vals sebenarnya tidak unik, tetapi biasanya masih memiliki beberapa baris per nilai. Jika Anda kurang beruntung ini bisa cukup untuk membuat pengoptimal berpikir pemindaian lengkap lebih murah ...
OK, jadi indeks gabungan dengan few_vals dan lots_vals hanya memiliki manfaat tepi kasus.
Bagaimana dengan kueri yang memfilter few_vals dan many_vals?
Indeks kolom tunggal hanya memberikan manfaat kecil. Tetapi gabungan mereka mengembalikan beberapa nilai. Jadi indeks komposit adalah ide yang bagus. Tapi ke arah mana?
Jika Anda menempatkan sedikit lebih dulu, mengompresi kolom terkemuka akan membuatnya lebih kecil
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
Dengan nilai yang berbeda lebih sedikit di kolom terkemuka kompres lebih baik. Jadi ada sedikit pekerjaan untuk membaca indeks ini. Namun hanya sedikit. Dan keduanya sudah merupakan bagian yang baik lebih kecil dari yang asli (ukuran 25% berkurang).
Dan Anda dapat melangkah lebih jauh dan kompres seluruh indeks!
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
Sekarang kedua indeks kembali ke ukuran yang sama. Perhatikan ini mengambil keuntungan dari fakta ada hubungan antara sedikit dan banyak. Sekali lagi tidak mungkin Anda akan melihat manfaat semacam ini di dunia nyata.
Sejauh ini kami hanya berbicara tentang pemeriksaan kesetaraan. Seringkali dengan indeks komposit Anda akan memiliki ketimpangan terhadap salah satu kolom. mis. pertanyaan seperti "dapatkan pesanan / pengiriman / faktur untuk pelanggan dalam N hari terakhir".
Jika Anda memiliki pertanyaan seperti ini, Anda ingin persamaan terhadap kolom pertama dari indeks:
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 |
----------------------------------------------------------------------------------------------------------------------
Perhatikan mereka menggunakan indeks yang berlawanan.
TL; DR
- Kolom dengan kondisi persamaan harus lebih dulu dalam indeks.
- Jika Anda memiliki beberapa kolom dengan persamaan dalam kueri Anda, menempatkan yang dengan nilai berbeda paling sedikit terlebih dahulu akan memberikan keuntungan kompresi terbaik
- Meskipun pemindaian lompatan indeks dimungkinkan, Anda harus yakin ini akan tetap menjadi opsi yang layak untuk masa mendatang
- Indeks komposit termasuk kolom hampir unik memberikan manfaat minimal. Pastikan Anda benar-benar perlu menyelamatkan 1-2 IO
1: Dalam beberapa kasus mungkin ada baiknya menyertakan kolom dalam indeks jika ini berarti semua kolom dalam kueri Anda ada dalam indeks. Ini memungkinkan hanya pemindaian indeks, sehingga Anda tidak perlu mengakses tabel.
2: Jika Anda memiliki lisensi untuk Diagnostik dan Tuning, Anda bisa memaksa rencana untuk melewati pemindaian dengan SQL Plan Management
ADDEDNDA
PS - dokumen yang Anda kutip dari 9i. Itu benar-benar tua. Saya akan tetap dengan sesuatu yang lebih baru