全表スキャンは邪悪な情報源ではなく、テーブルが比較的小さい場合にのみ、TomKyteに同意します。したがって、そのようなテーブルの追加のインデックスを持つことは冗長です。ただし、100.000レコードのテーブルは小さいと見なすべきではありませんが、そのようなテーブルからの計画を説明すると、実行されたテーブルの全表スキャンが示されます。そこで、Oracleをローカルにインストールしたラップトップで小さな実験を行いました。
1)最初に、my_tableを作成しました:
CREATE TABLE my_table(
"ID" NUMBER NOT NULL ENABLE,
"INVOICE_NO" VARCHAR2(10),
CONSTRAINT "test _PK" PRIMARY KEY ("ID")
)
2)次に、invoice_no列のインデックスを作成しました(これを使用してフィルタリングするため)。
CREATE INDEX "my_table_index1" ON my_table (invoice_no)
3)次に、100Kレコードを挿入します。
DECLARE
mod_val NUMBER;
BEGIN
FOR i IN 1..100000 LOOP
mod_val := MOD(i,6);
IF (mod_val = 0) THEN
INSERT INTO my_table (ID, INVOICE_NO) VALUES (i, '5570-110');
ELSIF (mod_val = 1) THEN
INSERT INTO my_table (ID, INVOICE_NO) VALUES (i, '5570-111');
ELSIF (mod_val = 2) THEN
INSERT INTO my_table (ID, INVOICE_NO) VALUES (i, '5570-112');
ELSIF (mod_val = 3) THEN
INSERT INTO my_table (ID, INVOICE_NO) VALUES (i, '5570-113');
ELSIF (mod_val = 4) THEN
INSERT INTO my_table (ID, INVOICE_NO) VALUES (i, '5570-114');
ELSIF (mod_val = 5) THEN
INSERT INTO my_table (ID, INVOICE_NO) VALUES (i, '5570-115');
END IF;
END LOOP;
COMMIT;
END;
4)次に、1つのランダムレコードを更新しました(選択を強調するためだけに):
BEGIN
UPDATE my_table SET INVOICENO = 'exception' WHERE id = 50000;
COMMIT;
END;
5)次に、説明プランを使用して選択を実行しました。
EXPLAIN PLAN FOR
SELECT * FROM my_table WHERE invoice_no = 'exception';
6)次に統計を取得しました:
SELECT * FROM TABLE(dbms_xplan.display);
7)そして結果を得た:
"PLAN_TABLE_OUTPUT"
"Plan hash value: 3804444429"
" "
"------------------------------------------------------------------------------"
"| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |"
"------------------------------------------------------------------------------"
"| 0 | SELECT STATEMENT | | 83256 | 1626K| 103 (1)| 00:00:02 |"
"| 1 | TABLE ACCESS FULL| MY_TABLE | 83256 | 1626K| 103 (1)| 00:00:02 |"
"------------------------------------------------------------------------------"
" "
"Note"
"-----"
" - dynamic sampling used for this statement (level=2)"
結論:それは奇妙で「魔法」の匂いがします。なぜOracleはinvoice_noフィールドのインデックスを使用しないことに決め、83256レコードをスキャンしたのでしょうか。私のラップトップは同時ユーザーで過負荷にならないことに同意します。テーブルのサイズはそれほど大きくありません(数値とvarcharが含まれています)が、この魔法は好きではなく、そのような動作の理由を知りたいです:)
更新:すべてのレコードのinvoice_noフィールドにダミー値(以下を参照)を追加しました-テーブルのサイズを大きくするためですが、テーブルの全表スキャンは残ります:
UPDATE2:分析テーブルも実行しましたが、結果は同じです:
ANALYZE TABLE my_table COMPUTE STATISTICS;
UPDATE3:インデックスの使用を強制しようとしましたが、結果は同じです(構文が間違っている可能性がありますか?):
EXPLAIN PLAN FOR
SELECT /*+ INDEX(my_table my_table_index1) */ * FROM my_table t WHERE invoice_no = 'exception'
UPDATE4:最後に、インデックスを使用するように「Oracleに指示」することができました-新しいgathertablestatsプロシージャを実行しました。
BEGIN
DBMS_STATS.GATHER_TABLE_STATS ( OWNNAME=>user
, TABNAME=>'my_table');
END;
Explainプランの出力は次のとおりです。
"--------------------------------------------------------------------------------------"
"| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |"
"-----------------------------------------------------------------------------------------------"
"| 0 | SELECT STATEMENT | | 1 | 294 | 5 (0)| 00:00:01 |"
"| 1 | TABLE ACCESS BY INDEX ROWID| MY_TABLE | 1 | 294 | 5 (0)| 00:00:01 |"
"|* 2 | INDEX RANGE SCAN | my_table_index1 | 1 | | 4 (0)| 00:00:01 |"
"-----------------------------------------------------------------------------------------------"
" "
"Predicate Information (identified by operation id):"
"---------------------------------------------------"
" "
" 2 - access(""INVOICE_NO""='exception')"
したがって、Oracleはある時点で何らかのクエリ手法を使用することを決定し、状況が変わっても更新していないようです。私はそれに同意しますが、選択を作成、挿入、実行したときに、このテストケースに適切なアプローチが選択されなかったのは不思議です。Oracleに最適なクエリ手法を使用するように指示するには、少なくとも最初は常にDBMS_STATS.GATHER_TABLE_STATSを実行する必要がありますか?