2009-08-13 16 views
2

私はOracle 10g Enterprise Editionを使用していると述べ、この質問を序文にします。Oracleのクエリを最適化するためのヘルプ

私は、次のスキーマを持つテーブルを持っている:

ID   integer (pk) -- unique index 
PERSON_ID integer (fk) -- b-tree index 
NAME_PART nvarchar  -- b-tree index 
NAME_PART_ID integer (fk) -- bitmap index 

PERSON_IDは、個人レコードの一意のIDの外部キーです。 NAME_PART_IDは、「名前」、「ミドルネーム」、「ラストネーム」などの静的な値を持つルックアップテーブルの外部キーです。テーブルのポイントは、人名の個々の部分を別々に保存することです。すべての人物のレコードには、少なくとも名前があります。データを引き出すしようとすると、私が最初にそうように、加入使っみなさ:

select 
    first_name.person_id, 
    first_name.name_part, 
    middle_name.name_part, 
    last_name.name_part 
from 
    NAME_PARTS first_name 
left join 
    NAME_PARTS middle_name 
    on first_name.person_id = middle_name.person_id 
left join 
    NAME_PARTS last_name 
    on first_name.person_id = last_name.person_id 
where 
    first_name.name_part_id = 1 
    and middle_name.name_part_id = 2 
    and last_name.name_part_id = 3; 

しかし、テーブルには、数十数百万レコードのを持っている、とNAME_PART_ID列のビットマップ索引が使用されていません。 Explainプランは、オプティマイザがフル・テーブル・スキャンとハッシュ・ジョインを使用してデータを取得していることを示しています。

提案がありますか?

編集テーブルがこのように設計された理由は、データベースがいくつかの異なる文化にまたがって使用されたためです(それぞれの中東文化では個人が通常最初の名前、次に父の名前、そして父の名前など)。文化的な違いをすべて考慮した複数の列を持つテーブルを作成することは困難です。

+0

私は、すべての名前の部分がperson_idがPKである表の列である、別のデザインを考えています。 –

+0

速度を上げるには、各パーツを独自のテーブルに入れることができます。データを取得する方法によって、常に1つのテーブルに複数の行が結合されている必要があります。彼らが自分の場所にいる場合、各パーツを見つけるのは簡単で、1つのテーブルで混乱させるのは直感的です。それ以外の場合は、スティーブの答えがおそらく最善です。 –

答えて

6

それだけのような、わずかに異なる形式のデータを返すようにクエリを書くことを検討してください:

SELECT person_id 
     , name_part_id 
     , name_part 
    FROM NAME_PART 
    WHERE name_part_id IN (1, 2, 3) 
ORDER BY person_id 
     , name_part_id; 

もちろん、あなたが3行の代わりに、それぞれの名前に1つになってしまいますが、それはかもしれませんあなたのクライアントコードがこれらを一緒にロールすることは自明です。

SELECT person_id 
     , max(decode(name_part_id, 1, name_part, null)) first 
     , max(decode(name_part_id, 2, name_part, null)) middle 
     , max(decode(name_part_id, 3, name_part, null)) last 
    FROM NAME_PART 
    WHERE name_part_id IN (1, 2, 3) 
GROUP BY person_id 
ORDER BY person_id; 

これは、元のクエリと同じ結果を生成します。これにより、3行を1つにまとめることができます。どちらのバージョンでは唯一参加する代わりに、3ウェイを扱うのは、一度(ソート付き)テーブルをスキャンします。 person_id索引の索引構成表を作成した場合は、ソート・ステップを保存します。

私は56150人とテーブルを使用してテストを実行し、ここでは結果の荒廃です:

オリジナルクエリ:

Execution Plan 
---------------------------------------------------------- 

------------------------------------------------------------------------------ 
| Id | Operation   | Name  | Rows | Bytes |TempSpc| Cost (%CPU)| 
------------------------------------------------------------------------------ 
| 0 | SELECT STATEMENT |   | 113K| 11M|  | 1364 (2)| 
|* 1 | HASH JOIN   |   | 113K| 11M| 2528K| 1364 (2)| 
|* 2 | TABLE ACCESS FULL | NAME_PART | 56150 | 1864K|  | 229 (3)| 
|* 3 | HASH JOIN   |   | 79792 | 5298K| 2528K| 706 (2)| 
|* 4 | TABLE ACCESS FULL| NAME_PART | 56150 | 1864K|  | 229 (3)| 
|* 5 | TABLE ACCESS FULL| NAME_PART | 56150 | 1864K|  | 229 (3)| 
------------------------------------------------------------------------------ 

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

    1 - access("FIRST_NAME"."PERSON_ID"="LAST_NAME"."PERSON_ID") 
    2 - filter("LAST_NAME"."NAME_PART_ID"=3) 
    3 - access("FIRST_NAME"."PERSON_ID"="MIDDLE_NAME"."PERSON_ID") 
    4 - filter("FIRST_NAME"."NAME_PART_ID"=1) 
    5 - filter("MIDDLE_NAME"."NAME_PART_ID"=2) 

Statistics 
---------------------------------------------------------- 
      0 recursive calls 
      0 db block gets 
     6740 consistent gets 
      0 physical reads 
      0 redo size 
    5298174 bytes sent via SQL*Net to client 
     26435 bytes received via SQL*Net from client 
     3745 SQL*Net roundtrips to/from client 
      0 sorts (memory) 
      0 sorts (disk) 
     56150 rows processed 

マイクエリ#1(3行/人):

Execution Plan 
---------------------------------------------------------- 

----------------------------------------------------------------------------- 
| Id | Operation   | Name  | Rows | Bytes |TempSpc| Cost (%CPU)| 
----------------------------------------------------------------------------- 
| 0 | SELECT STATEMENT |   | 168K| 5593K|  | 1776 (2)| 
| 1 | SORT ORDER BY  |   | 168K| 5593K| 14M| 1776 (2)| 
|* 2 | TABLE ACCESS FULL| NAME_PART | 168K| 5593K|  | 230 (3)| 
----------------------------------------------------------------------------- 

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

    2 - filter("NAME_PART_ID"=1 OR "NAME_PART_ID"=2 OR "NAME_PART_ID"=3) 

Statistics 
---------------------------------------------------------- 
      1 recursive calls 
      0 db block gets 
     1005 consistent gets 
      0 physical reads 
      0 redo size 
    3799794 bytes sent via SQL*Net to client 
     78837 bytes received via SQL*Net from client 
     11231 SQL*Net roundtrips to/from client 
      1 sorts (memory) 
      0 sorts (disk) 
    168450 rows processed 

マイクエリ#2(1行/人):

Execution Plan 
---------------------------------------------------------- 

----------------------------------------------------------------------------- 
| Id | Operation   | Name  | Rows | Bytes |TempSpc| Cost (%CPU)| 
----------------------------------------------------------------------------- 
| 0 | SELECT STATEMENT |   | 56150 | 1864K|  | 1115 (3)| 
| 1 | SORT GROUP BY  |   | 56150 | 1864K| 9728K| 1115 (3)| 
|* 2 | TABLE ACCESS FULL| NAME_PART | 168K| 5593K|  | 230 (3)| 
----------------------------------------------------------------------------- 

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

    2 - filter("NAME_PART_ID"=1 OR "NAME_PART_ID"=2 OR "NAME_PART_ID"=3) 

Statistics 
---------------------------------------------------------- 
      1 recursive calls 
      0 db block gets 
     1005 consistent gets 
      0 physical reads 
      0 redo size 
    5298159 bytes sent via SQL*Net to client 
     26435 bytes received via SQL*Net from client 
     3745 SQL*Net roundtrips to/from client 
      1 sorts (memory) 
      0 sorts (disk) 
     56150 rows processed 

が判明、あなたはまだ少し速くそれを絞ることができます。 person_idインデックスを強制的に使用するためのヒントを追加して、ソートを回避しようとしました。私は別の10%をオフにノックして管理し、それは並べ替えているように見える、それはまだ:

SELECT /*+ index(name_part,NAME_PART_person_id) */ person_id 
     , max(decode(name_part_id, 1, name_part)) first 
     , max(decode(name_part_id, 2, name_part)) middle 
     , max(decode(name_part_id, 3, name_part)) last 
    FROM name_part 
    WHERE name_part_id IN (1, 2, 3) 
GROUP BY person_id 
ORDER BY person_id; 

Execution Plan 
---------------------------------------------------------- 

----------------------------------------------------------------------------------------------------- 
| Id | Operation      | Name     | Rows | Bytes |TempSpc| Cost (%CPU)| 
----------------------------------------------------------------------------------------------------- 
| 0 | SELECT STATEMENT    |      | 56150 | 1864K|  | 3385 (1)| 
| 1 | SORT GROUP BY     |      | 56150 | 1864K| 9728K| 3385 (1)| 
| 2 | INLIST ITERATOR    |      |  |  |  |   | 
| 3 | TABLE ACCESS BY INDEX ROWID | NAME_PART    | 168K| 5593K|  | 2500 (1)| 
| 4 |  BITMAP CONVERSION TO ROWIDS|      |  |  |  |   | 
|* 5 |  BITMAP INDEX SINGLE VALUE | NAME_PART_NAME_PART_ID|  |  |  |   | 
----------------------------------------------------------------------------------------------------- 

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

    5 - access("NAME_PART_ID"=1 OR "NAME_PART_ID"=2 OR "NAME_PART_ID"=3) 

Statistics 
---------------------------------------------------------- 
      1 recursive calls 
      0 db block gets 
     971 consistent gets 
      0 physical reads 
      0 redo size 
    5298159 bytes sent via SQL*Net to client 
     26435 bytes received via SQL*Net from client 
     3745 SQL*Net roundtrips to/from client 
      1 sorts (memory) 
      0 sorts (disk) 
     56150 rows processed 

をしかし、上記の計画は、すべてあなたが全体のテーブルから選択しているという仮定に基づいています。あなたはPERSON_IDに基づいて結果を制限する場合(例えば、55968と56000の間PERSON_ID)、それはハッシュ参加すると、あなたの元のクエリが最速(一貫性のある106は、私は指定された制約のためになる27対)であることが判明しました。

上記のクエリを使用してカーソルを使用して結果セットをスクロールするGUIを作成している場合(結果セットの最初のN行のみが表示されます。 "and rowcount 50"述語)を追加すると、クエリの私のバージョンは再び高速になります(4つの一貫性のある取得と417)。

ストーリーの道徳は、あなたがデータへのアクセス方法に本当に正確に依存しているということです。異なるサブセットに対して適用すると、結果セット全体でうまく機能するクエリが悪化する可能性があります。

+0

+1、最後の段落の場合は他に何もない! – DCookie

+0

+1と緑色がチェックされています。優れた答えと洞察力に感謝します! –

5

あなたはどのような方法であなたのテーブルをフィルタリングしていないので、オプティマイザはHASH JOINがフィルタリングされていないテーブルを結合するための最良の方法である、おそらく正しいです。

この場合、ビットマップインデックスはあまり役に立ちません。

単一列の純粋なフィルタリングではなく、複数の低カーディナリティー列にORANDを作成するとよいです。

このため、完全なテーブルスキャンはほとんど常に優れています。

これは最適なデザインではありません。私はむしろfirst_namelast_namemiddle_nameの列をpersonに追加して、各列にインデックスを作成してNULLにすることができます。

この場合、デザインと同じテーブルがありますが、テーブルはありません。

インデックスは名前と全く同じようにテーブルがするようにrowidを保持し、ROWIDに参加はるかに効率的です。

アップデート:私自身は、個人名の一部として、父親の名前を使用しています文化のメンバーである

、私は三つのフィールドを使用すると、ほとんどの場合のために十分であると言うことができます。ファミリ名を

1つのフィールドは、与えられた名前のための1つのフィールドと(さらに専門化なし)の間のすべてのための1つのフィールドが名を処理するためにかなりまともな方法です。

ユーザーのみに頼ってください。現代の世界では、ほとんど誰もがこのスキーマに名前を適合させる方法を知っています。例えば

Family name: Picasso 
Given name: Pablo 
Middle name: Diego José Francisco de Paula Juan Nepomuceno María de los Remedios Cipriano de la Santísima Trinidad Ruiz y 

P. S.あなたは親しい友人がちょうど彼をPABLO~1と呼ばれることを知っていますか?

+0

+1:最初の文はそれほど大丈夫だと言います。 – DCookie

+0

提案していただきありがとうございます。私はテーブルスキーマを再設計することを考えました。オリジナルの質問に詳細を追加して、なぜそのオプションを使用しなかったのかを説明しました。 –

+1

私はあなたのP.S.のためにできるなら、私はあなたに別の+1を与えるだろう。 :-D – DCookie

-2

あなたは数百万行、使用よりも、すべての行をスキャンする方が簡単ですので、インデックスは無視されます(「ファーストネーム」について、「ミドルネーム」、「姓」)の3つの異なる値を持つインデックスを持っている場合そのインデックス。インデックスには、あらゆる用途に対応するために値の幅広い分布が必要です。

+2

-1:これはビットマップインデックス(name_part_idの場合)には当てはまりません。 http://www.oracle.com/technology/pub/articles/sharma_indexes.htmlには、ビットマップとbtreeの両方のインデックスに関するホワイトペーパーがあります。 – DCookie

+0

は、このクエリではビットマップであるかどうかのように見えます。このクエリでビットマップインデックスが保存できる魔法はありません。 –

+0

あなたの答えの最後の文は真実ではありません。これが私のdownvoteの理由です。 – DCookie

0

まあ、一つには、2番目の左側にあなたの代わりに「FIRST_NAME」との「first_token」と「last_token」を使用しているように見える参加「LAST_NAMEが。」私はこれはちょうどカットアンドペーストのエラーだと思っていますか?

+0

あなたは正しいです、それはコピーと過去のエラーでした。スキーマ作成者が実際にtoken_1、token_2などの名前を付けましたが、問題を明確にするために名前を変更していました。 –

3

+1私はQuassnoiの答えではありますが、このような場合に役立ちませんが(このように多くのレコードを検索しているため)、このテーブルはperson_idのハッシュクラスタにうまく格納できるため、レコード1人の人が同じブロック内に同じ場所に配置されているヒープ・テーブルよりもはるかに高速な構造のレコードを検索する場合は、あなたは、本質的に、あなたが、可能(あなたのクエリは最初、途中または最後だった名前の部分を持っていないいくつかの行を除く、このテーブルからすべてのデータを抽出していると)とにかくスキャン全表をやっていることを考えると

+0

これらのレコードをクラスタリングすると 'DML'が遅くなりますが、確かに' id'で人名を検索する速度が向上します。 '@ op'の場合に' DML'を遅くすることが許容できるかどうかは分かりませんが、このオプションは確かに考慮する必要があります。 +1 – Quassnoi

+0

それは実際には遅い挿入です(DMLで何が起こっているのか疑問に思うビットモデルがありますが)私はいつも私たちが読むよりはるかに少ない頻度で日付を修正することをいつも考えています。索引付けやクラスタリングなどによるDMLのオーバーヘッドは、より良い選択パフォーマンスとのバランスを取る必要があります。 –