私はcomposite indexes
について読んでいますが、注文について少し混乱しています。 このドキュメント (半分ほど下)は言う
一般に、最も頻繁に使用されると予想される列を最初にインデックスに配置する必要があります。
しかし、それが言った直後
最も選択的な列を最初にして、複合インデックスを作成します。つまり、値が最も多い列。
オラクルもそれを言う here 言い換えれば
すべてのキーがWHERE句で均等に頻繁に使用される場合、CREATE INDEXステートメントでこれらのキーを最も選択的なものから最も選択的でないものへと並べると、クエリのパフォーマンスが最も向上します。
ただし、別の言い方をする SO answer を見つけました。それは言う
選択性の最も低い列を最初に、最も選択性の高い列を最後に配置します。単独で使用される可能性が高いカラムとのタイリードの場合。
SO回答は、タイブレイキングのためだけにすべきであると述べていますが、順序も異なります。
これは documentation もskip scanning
について話し、言っています
複合インデックスの先頭列に異なる値がほとんどなく、インデックスの非先頭キーに異なる値が多い場合は、スキャンのスキップが有利です。
別の 記事 は言う
接頭辞列は、最も識別力があり、クエリで最も広く使用されている必要があります
これは、最も差別的であることは、最も独特であることを意味すると思います。
この調査のすべてが、依然として同じ質問に私を導きます。最も選択的な列は最初または最後にする必要がありますか?最初の列が最も使用され、タイブレイクで最も選択的である必要がありますか?
これらの記事は互いに矛盾しているようですが、いくつかの例を提供しています。私が収集したものから、least selective column
が順序でfirstである方がより効率的であると予想している場合、Index Skip Scans
。しかし、それが正しいかどうかは本当にわかりません。
From AskTom
(9iには、新しい「インデックススキップスキャン」があります。これを検索して、それについて読み取ります。これにより、インデックス(a、b)OR(b、a)が上記のケースの両方が時々!)
したがって、インデックス内の列の順序は、クエリの記述方法によって異なります。可能な限り多くのクエリでインデックスを使用できるようにしたい(所有しているインデックスの数全体を削減するため)-列の順序を制御します。他には何もありません(aまたはbの選択性はまったく考慮されません)。
複合インデックス内の列を、最も識別力の低い(値の異なる値が少ない)から最も識別力の高い(値の異なる値が高い)順に配置するための引数の1つは、インデックスキーの圧縮です。
_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
_
インデックス統計によると、最初のインデックスはより圧縮可能です。
もう1つは、クエリでのインデックスの使用方法です。クエリで主に_col1
_を使用する場合、
たとえば、次のようなクエリがある場合
select * from t where col1 = :a and col2 = :b;
_ select * from t where col1 = :a;
_ -then index(col1,col2)
はパフォーマンスが向上します。
クエリで主に_col2
_を使用する場合、
select * from t where col1 = :a and col2 = :b;
_select * from t where col2 = :b;
_ -then index(col2,col1)
はパフォーマンスが向上します。すべてのクエリで常に両方の列を指定する場合は、複合インデックスのどの列が最初であってもかまいません。
結論として、複合インデックスの列の順序に関する重要な考慮事項は、インデックスキーの圧縮と、クエリでこのインデックスを使用する方法です。
参照:
最も選択的な最初は、この列が実際のWHERE句にある場合にのみ役立ちます。
SELECTがより大きなグループ(選択性が低い)によるものであり、おそらく他のインデックス化されていない値によるものである場合、選択性の低い列を持つインデックスはまだ有用です(別のインデックスを作成しない理由がある場合)。
テーブルADDRESSがある場合、
COUNTRY CITY STREET、その他...
sTREET、CITY、COUNTRYのインデックスを作成すると、ストリート名を使用した最速のクエリが生成されます。しかし、都市のすべての通りをクエリすると、インデックスは役に立たなくなり、クエリはおそらく全表スキャンを実行します。
COUNTRY、CITY、STREETのインデックス作成は、個々の道路では少し遅くなる可能性がありますが、インデックスは他の国のクエリに使用でき、国や都市でのみ選択できます。
インデックス列の順序を選択するときの優先事項は次のとおりです。
クエリにこの列に対する(等価)述語はありますか?
列がwhere句に表示されない場合、それはindexing(1)の価値がありません
OK、これでテーブルと各列に対するクエリができました。時々複数。
インデックスを付ける対象をどのように決定しますか?
例を見てみましょう。これは3つの列を持つテーブルです。 1つは10個の値を保持し、もう1つは最後の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
これらは、ゼロが埋め込まれた左側の数値です。これは、後で圧縮についてのポイントを作成するのに役立ちます。
したがって、3つの一般的なクエリがあります。
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';
何を索引付けしますか?
ほんのわずかな__valsのインデックスは、全表スキャンよりわずかに優れています:
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 |
-------------------------------------------------------------------------------------------------------------
したがって、それだけでインデックスを作成する価値はありません。 lots_valsのクエリは数行を返します(この場合は1行のみ)。したがって、これは間違いなくインデックスを作成する価値があります。
しかし、両方の列に対するクエリはどうでしょうか?
インデックスを作成する必要があります:
( few_vals, lots_vals )
OR
( lots_vals, few_vals )
ひっかけ質問!
答えはどちらでもありません。
もちろん、few_valsは長い文字列です。だからあなたはそれから良い圧縮を得ることができます。そして、あなたは(fight_vals、lots_vals)を使用して、lots_valsにのみ述語を持つクエリのインデックススキップスキャンを取得する可能性があります。ただし、フルスキャンよりもパフォーマンスは著しく優れていますが、ここにはありません。
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 |
----------------------------------------------------------------------------------------------------------------------
ギャンブルは好きですか? (2)
したがって、先行列としてlots_valsを含むインデックスが必要です。そして、少なくともこの場合、複合インデックス(少数、ロット)は、単一(ロット)と同じ量の作業を行います
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 |
----------------------------------------------------------------------------------------------------------------------
複合インデックスにより、IOが1〜2節約される場合があります。しかし、この節約のために2つのインデックスを作成する価値はありますか?
また、複合インデックスには別の問題があります。 LOTS_VALSを含む3つのインデックスのクラスタ化係数を比較します。
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
Little_lotsのクラスタ化係数は、lotsおよびlots_fewの場合よりも10x高いことに注意してください。そして、これはそもそも完全なクラスタリングを備えたデモテーブルにあります。実際のデータベースでは、効果はさらに悪化する可能性があります。
それで何がそんなに悪いのですか?
クラスタリング係数は、インデックスの「魅力」を決定する主要な要因の1つです。値が高いほど、オプティマイザが選択する可能性が低くなります。特に、lots_valsが実際には一意ではないが、通常は値ごとに行がほとんどない場合。運が悪い場合は、これでオプティマイザにフルスキャンの方が安いと思わせることができます...
OK、したがって、fless_valsとlots_valsを含む複合インデックスには、Edgeケースのメリットしかありません。
Little_valsとmany_valsをフィルタリングするクエリはどうですか?
単一列のインデックスはわずかなメリットしかありません。しかし、それらを組み合わせると、いくつかの値が返されます。したがって、複合インデックスは良い考えです。しかし、どちらの方法ですか?
最初にいくつか配置する場合、先頭の列を圧縮すると、そのサイズが小さくなります
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
先頭列の異なる値が少ないほど、圧縮率が高くなります。したがって、このインデックスを読み取るための作業はわずかに少なくなります。しかし、ほんの少し。また、どちらも元のサイズよりも小さいチャンクです(サイズが25%減少)。
そしてさらに進んで、インデックス全体を圧縮できます!
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
これで、両方のインデックスが同じサイズに戻りました。これは少数と多数の間に関係があるという事実を利用していることに注意してください。繰り返しますが、現実の世界でこの種の利点が見られることはほとんどありません。
これまでは、等価チェックについてのみ説明してきました。多くの場合、複合インデックスを使用すると、列の1つに対して不平等が生じます。例えば「過去N日間の顧客の注文/出荷/請求書を取得する」などのクエリ。
これらの種類のクエリがある場合は、インデックスの最初の列に対する同等性が必要です。
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 |
----------------------------------------------------------------------------------------------------------------------
反対のインデックスを使用していることに注意してください。
TL; DR
1:場合によっては、クエリ内のすべての列がインデックス内にあることを意味する場合、インデックスに列を含めることは価値があります。これにより、インデックスのみのスキャンが可能になるため、テーブルにアクセスする必要はありません。
2:診断とチューニングのライセンスを取得している場合、SQLプラン管理を使用してプランを強制的にスキップスキャンすることができます。
[〜#〜] addednda [〜#〜]
PS-あなたがそこに引用したドキュメントは9iからのものです。それは非常に古いです。何かにこだわる 最近
列の選択性に加えて、複合インデックスが何を開始および/または含むべきかについての最終決定に寄与するクエリの要素が他にもあります。
例えば:
会話を適切に保つには、以下の私の答えが次の状況に当てはまります。
私の経験では、DBAが注意する必要があるのはどちらでもあります。
唯一のルールが適用されているとしましょう:
1)最も選択的な列を最初にしてインデックスを作成したが、その列は実際にはそのテーブルのほとんどのクエリで使用されておらず、dbエンジンでは使用されていない場合。
2)クエリで最も広く使用されている列を先頭にしてインデックスを作成したが、列の選択性が低い場合も、クエリのパフォーマンスが低下する。
主にテーブルクエリの90%で使用される列をリストします。次に、カーディナリティが最も高いカーディナリティから最も低いカーディナリティの順に並べます。
読み取りクエリのパフォーマンスを向上させるためにインデックスを使用し、そのワークフロー(読み取りクエリのタイプ)はインデックスの作成のみを駆動します。実際、データが増えると(数十億行)圧縮されたインデックスはストレージを節約する可能性がありますが、読み取りクエリのパフォーマンスを低下させます。
理論的には、最も選択的なカラムが最も高速な検索になります。しかし、職場では、3つの部分の複合インデックスがあり、最初に最も選択的な部分があるという状況に遭遇しました。 (日付、著者、出版社がこの順序で言うと、テーブルは投稿の評価を監視します)そして、3つの部分すべてを使用するクエリがあります。 Mysqlはデフォルトでは、著者のonlnyインデックスを使用し、クエリに存在するにもかかわらず、会社と日付を含む複合インデックスをスキップします。強制インデックスを使用してコンポジットを使用すると、クエリの実行速度が実際に遅くなります。なぜそれが起こったのですか?私はあなたに伝えましょう:
私は日付に範囲を選択していたので、日付は非常に選択的ですが、範囲スキャンにそれを使用しているという事実(範囲は比較的短く、6年間のデータのうち6か月)は、複合を有害にしましたmysql。その特定のケースでコンポジットを使用するには、mysqlは新しい年以降に書かれたすべての記事を取得してから著者が誰であるかを調べる必要があり、著者が他の著者と比較してそれほど多くの記事を書いていないことを考えると、mysqlはその著者を見つけるだけを好んだ。
別のケースでは、コンポジットでクエリがはるかに高速に実行されました。ケースは、作者が非常に人気があり、ほとんどのレコードを所有していて、日付によるソートが理にかなっている場合でした。しかし、mysqlはそのケースを自動検出しませんでした。インデックスを強制する必要がありました。範囲スキャンにより、選択した列が役に立たなくなる可能性があります。データの分布により、列が異なるレコードに対してより選択的になる場合があります...
私が別の方法で行うことは、日付(これも理論的には最も選択的です)を右にシフトすることです。これで、範囲スキャンを実行することになるので、違いが出ます。