Dizin sütunu sırasını seçerken, geçersiz kılma endişesi:
Sorularımda bu sütuna karşı (eşitlik) tahminleri var mı?
Bir sütun hiçbir yerde bir cümlede görünmezse, dizine eklemeye değmez (1)
Tamam, böylece her sütuna karşı bir tablo ve sorgularınız var. Bazen birden fazla.
Neyin endeksleneceğine nasıl karar verirsiniz?
Bir örneğe bakalım. İşte üç sütunlu bir tablo. Biri 10, diğeri 1.000, son 10.000 değeri tutar:
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
Bunlar sıfırlarla doldurulmuş sayılardır. Bu, daha sonra sıkıştırma ile ilgili noktayı belirtmeye yardımcı olacaktır.
Yani üç yaygın sorgunuz var:
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';
Neyi endekslersin?
Yalnızca birkaç_değerdeki bir dizin, tam tablo taramasından yalnızca marjinal olarak daha iyidir:
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 |
-------------------------------------------------------------------------------------------------------------
Bu yüzden kendi başına endekslemeye değecek gibi görünmüyor. Lots_vals üzerindeki sorgular birkaç satır döndürür (bu durumda yalnızca 1). Bu kesinlikle endekslemeye değer.
Peki her iki sütuna karşı sorgular ne olacak?
Dizine eklemeniz gerekir:
( few_vals, lots_vals )
VEYA
( lots_vals, few_vals )
Tuzak soru!
Cevap da değil.
Tabii few_vals uzun bir dizedir. Böylece iyi bir sıkıştırma elde edebilirsiniz. Ve yalnızca lots_vals için tahminleri olan (az_değer, çok_değer) kullanarak sorgular için bir dizin atlama taraması alabilirsiniz (olabilir). Ancak tam bir taramadan belirgin şekilde daha iyi performans göstermesine rağmen burada değilim:
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 |
----------------------------------------------------------------------------------------------------------------------
Kumar oynamayı sever misin? (2)
Bu nedenle, önde gelen sütun olarak lots_vals içeren bir dizine ihtiyacınız vardır. Ve en azından bu durumda bileşik endeksi (birkaç, lot) sadece (lotlar) ile aynı miktarda iş yapar
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 |
----------------------------------------------------------------------------------------------------------------------
Bileşik endeksin size 1-2 IO kazandırdığı durumlar olacaktır. Ancak bu tasarruf için iki endekse sahip olmaya değer mi?
Bileşik endeks ile ilgili başka bir sorun daha var. LOTS_VALS dahil üç dizin için kümeleme faktörünü karşılaştırın:
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
Few_lots için kümeleme faktörünün lot ve lots_few için olduğundan 10 kat daha yüksek olduğuna dikkat edin ! Ve bu, başlamak için mükemmel kümelenmeye sahip bir demo tablosunda. Gerçek dünya veritabanlarında etkinin daha kötü olması muhtemeldir.
Peki bunda kötü olan ne?
Kümeleme faktörü, bir endeksin ne kadar "çekici" olduğunu belirleyen temel faktörlerden biridir. Ne kadar yüksek olursa, optimize edicinin onu seçme olasılığı o kadar düşük olur. Özellikle lots_val değerleri aslında benzersiz değilse de, normalde değer başına birkaç satır varsa. Şanssızsanız, bu, optimize edicinin tam bir taramanın daha ucuz olduğunu düşünmesini sağlamak için yeterli olabilir ...
Tamam, bu nedenle az_değer ve çok_değer içeren bileşik dizinlerin yalnızca kenar durumu avantajları vardır.
Few_vals ve many_vals filtreleme sorgularına ne olacak?
Tek sütun dizinleri yalnızca küçük avantajlar sağlar. Ancak kombine edildiğinde birkaç değer döndürürler. Dolayısıyla bileşik bir endeks iyi bir fikirdir. Ama hangi yönden?
İlk önce birkaç tane yerleştirirseniz, önde gelen sütunu sıkıştırmak
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
Önde gelen sütundaki daha az farklı değerle daha iyi sıkıştırır. Yani bu endeksi okumak için çok az iş var. Ama sadece biraz. Ve her ikisi de zaten orijinalinden daha iyi bir yığın (% 25 boyut küçültme).
Ve daha ileri gidip tüm endeksi sıkıştırabilirsiniz!
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
Şimdi her iki dizin de aynı boyutta. Bunun az ve çok arasında bir ilişki olduğu gerçeğinden faydalandığını unutmayın. Yine, gerçek dünyada bu tür bir fayda görmeniz pek olası değildir.
Şimdiye kadar sadece eşitlik kontrolleri hakkında konuştuk. Genellikle bileşik dizinlerde sütunlardan birine karşı eşitsizliğe sahip olursunuz. örneğin "son N gün içinde bir müşteri için siparişleri / gönderileri / faturaları alın" gibi sorgular.
Bu tür sorgularınız varsa, dizinin ilk sütununda eşitliği istiyorsanız:
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 |
----------------------------------------------------------------------------------------------------------------------
Karşı indeksi kullandıklarına dikkat edin.
TL; DR
- Eşitlik koşullarına sahip sütunlar dizinde ilk sırada yer almalıdır.
- Sorgunuzda eşitliklere sahip birden çok sütun varsa, en az farklı değere sahip sütunu ilk sıraya yerleştirmek en iyi sıkıştırma avantajını sağlar
- İndeks atlama taramaları mümkün olmakla birlikte, bunun öngörülebilir gelecek için uygun bir seçenek olarak kalacağından emin olmanız gerekir.
- Neredeyse benzersiz sütunlar içeren bileşik dizinler, minimum fayda sağlar. 1-2 IO'yu gerçekten kaydetmeniz gerektiğinden emin olun
1: Bazı durumlarda, sorgunuzdaki tüm sütunların dizinde olduğu anlamına gelirse, dizine bir sütun eklemeye değer olabilir. Bu, yalnızca bir dizin taramasını etkinleştirir, bu nedenle tabloya erişmeniz gerekmez.
2: Tanılama ve Ayarlama lisansına sahipseniz, planı SQL Plan Yönetimi ile atlamayı zorlamaya zorlayabilirsiniz
ADDEDNDA
PS - orada alıntıladığınız dokümanlar 9i'den. Bu gerçekten yaşlı. Daha yeni bir şeye sadık kalırım