テーブル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:
Plan2:
インデックスフルスキャンは、インデックスの片側のみをアクセスできます。あなたがしているとき
SELECT MIN(SDATE), MAX(SDATE) FROM MYTABLE
あなたは2つの側面を訪問することを要求しています。したがって、列の最小値と最大値の両方が必要な場合、インデックスのフルスキャンは実行できません。
あなたが見つけることができるより詳細な分析 ここ 。
Explainプランは異なります。単一のMIN
またはMAX
はINDEX 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つの異なるクエリで最小値と最大値を検索して、独自の最適化を実行できます。
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)のインデックスにアクセスします。
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つの理由は次のとおりです。
- 全表スキャンを実行する方が高速です。
- 貧弱な統計。
あなたが質問に投稿していないものがない限り、私の直接的な答えは、このテーブルで統計を収集していない、十分に高い推定パーセントでそれらを収集していない、または使用したことです analyze
、これは dbms_stats.gather_table_stats
とは異なり、コストベースオプティマイザーをしないです。
analyze
のドキュメントから引用するには:
ほとんどの統計の収集には、DBMS_STATSパッケージを使用します。これにより、統計を並行して収集し、パーティション化されたオブジェクトのグローバル統計を収集し、他の方法で統計収集を微調整できます。 DBMS_STATSパッケージの詳細は、 『Oracle Database PL/SQLパッケージおよびタイプ・リファレンス』を参照してください。
コストベースのオプティマイザに関連しない統計収集には、(DBMS_STATSではなく)ANALYZEステートメントを使用します。