web-dev-qa-db-ja.com

複合インデックス(item、item_type)またはitem_type_1、item_type_2、...の単一列インデックスの方が優れていますか?

いくつかのビジネスロジックに従って格納およびクエリされる3つのタイプのアイテムがあります。

create table a_table
(
   item_a   varchar2(30),
   item_b   varchar2(16),
   item_c   varchar2(2),
   -- other columns
);

そしてインデックス

create unique index idx_1 on a_table (item_a);
create unique index idx_2 on a_table (item_b);
create unique index idx_3 on a_table (item_c);

特定のタイプの特定のアイテムのデータは、次のように簡単に読み取られます。

-- reading item_a
select ... from a_table where item_a = '...';

-- reading item_b
select ... from a_table where item_b = '...';

-- ...

アイテムのタイプがaの場合、タイプbにすることはできないため、各行にはitem_aの1つのみが必要です、item_bまたはitem_cに値を指定し、その他はnullにする必要があります。 (これはどのような方法でも強制できます。insertionsに影響するかどうかは問題ではありません。)

理論的には、アイテムのタイプの数は増える可能性があります(4番目のアイテムタイプが将来どこかに追加される可能性があります)。

この事実は、醜い相互に排他的な(与えられた例ではチェックされていない)列を回避する別のソリューションに向かってプッシュされました。

create table a_table
(
   item       varchar2(30) not null,
   item_type  varchar2(10) not null,
   -- other columns
);

create unique index idx_1 on a_table (item_type, item);

-- reading item_X
select ... from a_table where item='...' and item_type='item_X';

項目タイプの数が増加した場合の最初のソリューションの明らかな欠点とその他の考えられる設計上の欠陥を無視して、アクセス間に重要なパフォーマンスの違いはありますか(インデックス付き列を使用した行の選択)a最初の場合と同じようにテーブルにアクセスし、2番目の場合と同じようにテーブルにアクセスする

item_aおよびitem_bのカーディナリティーは数百万になる可能性があります。item_cのカーディナリティーは確かに200未満です。

ノート

  • item_aitem_b、およびitem_cの選択数が等しいと仮定します。
  • 最初のソリューションでは、複合インデックス(item_c, item_a, item_b)iffを検討します。3つの個別のインデックスよりも(パフォーマンスの点で)優れていますが、両方のインデックス付きの列がある2番目のソリューションで比較を行う必要があることを忘れないでください。 where条件で使用されます。両方のケースで最良の「正しい」インデックスを使用すると想定します。
3
ShinTakezou

パフォーマンスの観点からは、非常に近いと思います。

しかし、いくつかの考え:

  1. 1つの列インデックスはNULL値を格納しません。したがって、個々のインデックスは小さくなります。複合インデックス(item_type、item)のサイズは、個々のインデックスの合計よりも大きくなります。極端な場合、インデックスの高ささえ異なる場合があります。

  2. 3つの個別のインデックスのうち2つは一意になるため、一意でないインデックスと比較して、読み取りの一貫性が1つ少なくなります。

  3. Item_c列​​の場合、Oracleは正確なヒストグラムを作成できます。

  4. (item_type、item)の場合、OracleはアイテムタイプAおよびBのクエリのカーディナリティを正しく計算しますが、「item_type = 'C'およびitem =:X」の場合は過小評価される可能性があります。これは問題ではない可能性がありますが、結合を使用した複雑なクエリでは、効果のない計画になる可能性があります。確認する必要があります。

  5. インデックス(item_a、item_b、item_c)を(item_type、item)と比較しようとしています-他の人は、「item_a =:X」のようなクエリでのみ機能し、「item_b =:Y」の場合は機能しないと述べています。したがって、item_b列とitem_c列​​に同じ数のクエリがある場合、最初のケースにインデックス(item_a、item_b、item_c)を使用することは、単に悪い考えです。

    create table hr.tt nologging
    as
    select case when mod(rownum,3) = 0 then rownum end AS item_a,
           case when mod(rownum,3) = 1 then rownum end AS item_b,
           case when mod(rownum,3) = 2 then round(dbms_random.value(1,200)) end AS item_c,
           o.* 
     from all_objects o, 
    (select * from dual connect by level < 100) d 
     where rownum <= 5e6; 

    create unique index hr.item_a_idx on hr.tt(item_a) nologging;

    create unique index hr.item_b_idx on hr.tt(item_b) nologging;      

    create index hr.item_c_idx on hr.tt(item_c) nologging;


    create table hr.tt2 nologging
    as
    select case when mod(rownum,3) in (0,1) then rownum
                else round(dbms_random.value(1,200)) 
                end AS item,
           case when mod(rownum,3) = 0 then 'A' 
                when mod(rownum,3) = 1 then 'B' 
                when mod(rownum,3) = 2 then 'C' end AS item_type,
           o.* 
     from all_objects o, 
    (select * from dual connect by level < 100) d 
     where rownum <= 5e6;

    create index hr.item_item_type_idx on hr.tt2(item_type, item) nologging;

いくつかのクエリ

SYS@mydb> select * from hr.tt where item_a = 300;


------------------------------------------------------------------------------------------
| Id  | Operation                   | Name       | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |            |     1 |   197 |     3   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| TT         |     1 |   197 |     3   (0)| 00:00:01 |
|*  2 |   INDEX UNIQUE SCAN         | ITEM_A_IDX |     1 |       |     2   (0)| 00:00:01 |
------------------------------------------------------------------------------------------
Statistics
----------------------------------------------------------
...
          4  consistent gets
SYS@mydb> select * from hr.tt2 where item_type = 'A'  and item = 300;

1 row selected.


--------------------------------------------------------------------------------------------------
| Id  | Operation                   | Name               | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |                    |     1 |   115 |     4   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| TT2                |     1 |   115 |     4   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | ITEM_ITEM_TYPE_IDX |     1 |       |     3   (0)| 00:00:01 |
--------------------------------------------------------------------------------------------------

Statistics
----------------------------------------------------------
          5  consistent gets

以下は、オプティマイザの計算が間違っている例です(Oracle 11.2)。オプティマイザーは、 "INDEX RANGE SCAN"に1行しかないと考えます。実際には8438行あります。これら2つの列の拡張オプティマイザ統計は役立つ場合とそうでない場合があります。

SYS@mydb> select * from hr.tt2 where item_type = 'C'  and item = 150;

8438 rows selected.


Execution Plan
----------------------------------------------------------
Plan hash value: 4063424880

--------------------------------------------------------------------------------------------------
| Id  | Operation                   | Name               | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |                    |     1 |   115 |     4   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| TT2                |     1 |   115 |     4   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | ITEM_ITEM_TYPE_IDX |     1 |       |     3   (0)| 00:00:01 |
--------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("ITEM_TYPE"='C' AND "ITEM"=150)


Statistics
----------------------------------------------------------
          8  recursive calls
          0  db block gets
       8618  consistent gets
       . . .
       8438  rows processed

以下は、個々のitem_c列​​の同じケースです。ここでオプティマイザの計算は正しいです-推定8133行、8290行が選択されています。

SYS@mydb> select * from hr.tt where item_c = 150;

8290 rows selected.


Execution Plan
----------------------------------------------------------
------------------------------------------------------------------------------------------
| Id  | Operation                   | Name       | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |            |  8133 |   826K|  7737   (1)| 00:01:33 |
|   1 |  TABLE ACCESS BY INDEX ROWID| TT         |  8133 |   826K|  7737   (1)| 00:01:33 |
|*  2 |   INDEX RANGE SCAN          | ITEM_C_IDX |  8133 |       |    19   (0)| 00:00:01 |
------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("ITEM_C"=150)


Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
       8499  consistent gets
       . . .
       8290  rows processed
4
Eduard Okhvat

私はOracleを話しませんが、最初のシナリオでは、論理的またはパフォーマンスの面で利点はありません。 @ brian-leachがコメントで示唆しているように、相互排他性を強制する必要がありますが、挿入が遅くなります。

このシナリオを使用すると、item_dをコレクションに追加することについて心配するだけでなく、item_xが追加の属性を取得するとどうなりますか?モデル2は適切な方法で簡単に拡張でき、モデル1はnull可能な属性の地獄の穴になります

あなたは明らかに、2番目のオプションの方が優れていると強く確信しています(強く同意します)。どちらのモデルでも、1000000 item_a、1000000 item_b、1000 item_cを生成するのは簡単な作業です。

2
Lennart

いつものように、それはすべて、データへのアクセス方法accessに依存します。

Item_1でのみテーブルをクエリする場合、他の2つにインデックスを付けるのは時間の無駄になります。

あなたが最も一般的にテーブルをitem_1でクエリし、sometimesをitem_2同様に、item_1とitem_2の複合インデックスは良いアイデアかもしれません。データベースは、item_1のみのクエリにも複合インデックスを使用できますが、あなたdoは、あなた最も頻繁にクエリによって。

調べる最善の方法は? テストしてください

テストデータベースを起動し、いくつかの代表的なデータを読み込み、いくつかのインデックスを作成し、一般的なクエリの実行計画を取得します。

Item/item_typeテーブルは、「エンティティ/属性値」と呼ばれる概念を使用しており、一般に、スケーリングが非常に悪いです。適切なインデックス作成により、パフォーマンスが向上します。

1
Phill W.