web-dev-qa-db-ja.com

テーブルからMINとMAXの両方を選択すると、予想よりも遅くなります

テーブルMYTABLEがあり、日付列SDATEがテーブルの主キーであり、一意のインデックスが付いています。

このクエリを実行すると:

SELECT MIN(SDATE) FROM MYTABLE

それは即座に答えを与えます。同じことが起こります:

SELECT MAX(SDATE) FROM MYTABLE

しかし、両方を一緒にクエリすると:

SELECT MIN(SDATE), MAX(SDATE) FROM MYTABLE

実行にははるかに時間がかかります。私は計画を分析し、minまたはmaxの1つがクエリされると、それはINDEX FULL SCAN(MIN/MAX)を使用しますが、両方が同時にクエリされると、FULL TABLE SCANを実行します。

どうして?

テストデータ:

バージョン 11g

create table MYTABLE
(
  SDATE  DATE not null,
  CELL   VARCHAR2(10),
  data NUMBER
)
tablespace CHIPS
  pctfree 10
  pctused 40
  initrans 1
  maxtrans 255
  storage
  (
    initial 64K
    minextents 1
    maxextents unlimited
  );

alter table MYTABLE
  add constraint PK_SDATE primary key (SDATE)
  using index 
  tablespace SYSTEM
  pctfree 10
  initrans 2
  maxtrans 255
  storage
  (
    initial 64K
    minextents 1
    maxextents unlimited
  );

テーブルをロード:

declare 
  i integer;
begin
  for i in 0 .. 100000 loop
     insert into MYTABLE(sdate, cell, data)
     values(sysdate - i/24, 'T' || i, i);     
     commit;
  end loop;
end;

統計を収集:

begin
  dbms_stats.gather_table_stats(tabname => 'MYTABLE', ownname => 'SYS');
end;

計画1:

enter image description here

Plan2:

enter image description here

26
RGO

インデックスフルスキャンは、インデックスの片側のみをアクセスできます。あなたがしているとき

SELECT MIN(SDATE), MAX(SDATE) FROM MYTABLE

あなたは2つの側面を訪問することを要求しています。したがって、列の最小値と最大値の両方が必要な場合、インデックスのフルスキャンは実行できません。

あなたが見つけることができるより詳細な分析 ここ

12
avi

Explainプランは異なります。単一のMINまたはMAXINDEX FULL SCAN (MIN/MAX)を生成しますが、2つが存在する場合は_INDEX FULL SCAN_または _FAST FULL INDEX SCAN_ を取得します。

違いを理解するには、 _FULL INDEX SCAN_ の説明を探す必要があります。

全索引スキャンでは、データベースは索引全体を順番に読み取ります。

言い換えると、インデックスが_VARCHAR2_フィールドにある場合、Oracleは、たとえば「A」で始まるすべてのエントリを含むインデックスの最初のブロックをフェッチし、すべてのエントリをアルファベット順にブロックごとに読み取ります。最後のエントリ( "A"から "Z")まで。エントリはバイナリツリーインデックスでソートされるため、Oracleはこの方法で処理できます。

EXPLAIN PLANにINDEX FULL SCAN (MIN/MAX)が表示された場合、これは、エントリがソートされているため、最初のエントリを読み取った後で、MINのみに関心がある場合は停止できるという事実を使用した最適化の結果です。 。 MAXのみに関心がある場合、Oracleは同じアクセスパスを使用できますが、今回は最後のエントリから始まり、「Z」から「A」に逆方向に読み取ります。

現在のところ、_FULL INDEX SCAN_には一方向のみ(順方向または逆方向)があり、両端から同時に開始することはできません。そのため、最小値と最大値の両方を要求すると、効率の悪いアクセス方法になります。 。

他の回答で示唆されているように、クエリに非常に高い効率が必要な場合は、2つの異なるクエリで最小値と最大値を検索して、独自の最適化を実行できます。

6
Vincent Malgrat

1つのクエリでインデックスの両方のエッジを選択しないようにしてください。次のような別の方法でクエリにアクセスします。

select max_date, min_date
from (select max(sdate) max_date from mytable),
       (select min(sdate) min_date from mytable)

オプティマイザはネストされたループ(この場合は2回)でINDEX_FULL_SCAN(MIN/MAX)のインデックスにアクセスします。

enter image description here

5
planben

11.2で同じ動作が見られないことを言わなければなりません

次のようにテストケースを設定し、Vincentのコメントに応じて10k行から1m行に更新した場合

set linesize 130
set pagesize 0
create table mytable ( sdate date );

Table created.

insert into mytable
 select sysdate - level
   from dual
connect by level <= 1000000;
commit;

1000000 rows created.


Commit complete.

alter table mytable add constraint pk_mytable primary key ( sdate ) using index;

Table altered.

begin
dbms_stats.gather_table_stats( user, 'MYTABLE' 
                             , estimate_percent => 100
                             , cascade => true
                               );
end;
/

PL/SQL procedure successfully completed.

次に、私が取得するクエリを実行してほぼ同じように見えるExplainプラン(異なるタイプのINDEX FULL SCANに注意してください)

explain plan for select min(sdate) from mytable;

Explained.

select * from table(dbms_xplan.display);
Plan hash value: 3877058912

-----------------------------------------------------------------------------------------
| Id  | Operation          | Name   | Rows  | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT       |        |     1 |     8 |     1   (0)| 00:00:01 |
|   1 |  SORT AGGREGATE        |        |     1 |     8 |        |      |
|   2 |   INDEX FULL SCAN (MIN/MAX)| PK_MYTABLE |     1 |     8 |     1   (0)| 00:00:01 |
-----------------------------------------------------------------------------------------

9 rows selected.

explain plan for select min(sdate), max(sdate) from mytable;

Explained.

select * from table(dbms_xplan.display);
Plan hash value: 3812733167

-------------------------------------------------------------------------------
| Id  | Operation    | Name       | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT |        |     1 |     8 |   252   (0)| 00:00:04 |
|   1 |  SORT AGGREGATE  |        |     1 |     8 |        |          |
|   2 |   INDEX FULL SCAN| PK_MYTABLE |  1000K|  7812K|   252   (0)| 00:00:04 |
-------------------------------------------------------------------------------

9 rows selected.

私の以前の答えから引用するには:

クエリがインデックスを使用しない最も一般的な2つの理由は次のとおりです。

  1. 全表スキャンを実行する方が高速です。
  2. 貧弱な統計。

あなたが質問に投稿していないものがない限り、私の直接的な答えは、このテーブルで統計を収集していない、十分に高い推定パーセントでそれらを収集していない、または使用したことです analyze 、これは dbms_stats.gather_table_stats とは異なり、コストベースオプティマイザーをしないです。

analyzeのドキュメントから引用するには:

ほとんどの統計の収集には、DBMS_STATSパッケージを使用します。これにより、統計を並行して収集し、パーティション化されたオブジェクトのグローバル統計を収集し、他の方法で統計収集を微調整できます。 DBMS_STATSパッケージの詳細は、 『Oracle Database PL/SQLパッケージおよびタイプ・リファレンス』を参照してください。

コストベースのオプティマイザに関連しない統計収集には、(DBMS_STATSではなく)ANALYZEステートメントを使用します。

2
Ben