web-dev-qa-db-ja.com

条件としてのネストされたテーブルがインデックスの使用を妨げています

サブクエリに参加する次のクエリは、TABLE ACCESS FULLマスターテーブルがネストされたテーブルを条件として使用する場合。条件が単純な場合=、同じクエリでINDEX RANGE SCAN 予想通り。 (以下のDDL)

ネストしたテーブルを含むクエリでインデックスが使用されないのはなぜですか?

SELECT m.id, sub.cnt
  FROM m
  LEFT JOIN (
    SELECT d.m_id, COUNT(1) AS cnt -- (+ other functions)
      FROM d
     GROUP BY d.m_id
  ) sub ON ( sub.m_id = m.id )
 WHERE m.id IN ( SELECT /*+ CARDINALITY(tab,1) */ COLUMN_VALUE FROM TABLE( NEW t(1) ) tab )
-- WHERE m.id = 1 -- alternative condition

状態 m.id = 1インデックスを使用dに対して期待どおり:

----------------------------------------------------------------------------
| Id | Operation           | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------
|  0 | SELECT STATEMENT    |       |  1000K|    37M|     1   (0)| 00:00:01 |
|  1 |  NESTED LOOPS OUTER |       |  1000K|    37M|     1   (0)| 00:00:01 |
|* 2 |   INDEX UNIQUE SCAN | PK_M  |     1 |    13 |     0   (0)| 00:00:01 |
|  3 |   VIEW              |       |  1000K|    24M|     1   (0)| 00:00:01 |
|  4 |    SORT GROUP BY    |       |  1000K|    12M|     1   (0)| 00:00:01 |
|* 5 |     INDEX RANGE SCAN| IDX_D |  1000K|    12M|     1   (0)| 00:00:01 |
----------------------------------------------------------------------------

ネストしたテーブルの使用インデックスを使用しない

------------------------------------------------------------------------------------------------------
| Id | Operation                                | Name | Rows  | Bytes |TempSp|Cost (%CPU)| Time     |
------------------------------------------------------------------------------------------------------
|  0 | SELECT STATEMENT                         |      |  1000K|    39M|      |  185K  (4)| 00:37:05 |
|* 1 |  HASH JOIN OUTER                         |      |  1000K|    39M| 2640K|  185K  (4)| 00:37:05 |
|  2 |   MERGE JOIN SEMI                        |      |   100K|  1464K|      |   30   (4)| 00:00:01 |
|  3 |    INDEX FULL SCAN                       | PK_M |    10M|   123M|      |    0   (0)| 00:00:01 |
|* 4 |    SORT UNIQUE                           |      |     1 |     2 |      |   30   (4)| 00:00:01 |
|  5 |     COLLECTION ITERATOR CONSTRUCTOR FETCH|      |     1 |     2 |      |   29   (0)| 00:00:01 |
|  6 |   VIEW                                   |      |   100M|  2479M|      | 4898 (100)| 00:00:59 |
|  7 |    HASH GROUP BY                         |      |   100M|  1239M|      | 4898 (100)| 00:00:59 |
|  8 |     TABLE ACCESS FULL                    | D    |   100M|  1239M|      |  571  (95)| 00:00:07 |
------------------------------------------------------------------------------------------------------

[〜#〜] ddl [〜#〜]

CREATE TABLE m ( id NUMBER);
  ALTER TABLE m ADD CONSTRAINT pk_m PRIMARY KEY ( id );
CREATE TABLE d ( m_id NUMBER );
  CREATE INDEX idx_d ON d ( m_id );

CREATE TYPE t AS TABLE OF NUMBER;

EXEC dbms_stats.set_table_stats ( sys_context( 'userenv', 'current_schema' ),
                                  'M', numrows => 1000 );
EXEC dbms_stats.set_table_stats ( sys_context( 'userenv', 'current_schema' ),
                                  'D', numrows => 10000 );
3
Peter Lang

うーん・・・これは面白いですね。 Oracleオプティマイザーは、私のような死すべき者に関する限り、ブラックボックスのようなものです...ジョナサンルイスは、このトピックについて536ページ book を記述しました。就寝時間の読書ではありません!).

2つの質問。

a)各クエリの前後でキャッシュをフラッシュしましたか?最初のクエリが2番目のクエリに影響を与える可能性があります。

b)これらの数値を「M」と「D」に増やすとどうなりますか? INDEXスキャンに戻る点はありますか?私はそのようなポイントが来ると思います(しかし、ポイントaを参照してください)

ほんのいくつかの考え-私は渡すOracleシステムを持っていません。

1
Vérace

私は何をしているのかわからない™が、次の同等のクエリでうまくいくようです(少なくとも、クエリを編集してLEFT JOIN...を使用する前は同等でした)。

SELECT m.id, sub.cnt
  FROM m
  JOIN (
    SELECT d.m_id, COUNT(1) AS cnt -- (+ other functions)
      FROM d
     WHERE d.m_id IN ( SELECT COLUMN_VALUE FROM TABLE( NEW t(1) ) )
     GROUP BY d.m_id
  ) sub ON ( sub.m_id = m.id )

それは私に与えるでしょう:

----------------------------------------------------------------------------------------------------
| Id  | Operation                                  | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                           |       |       |       |    31 (100)|          |
|   1 |  NESTED LOOPS                              |       |     1 |    39 |    31   (7)| 00:00:01 |
|   2 |   VIEW                                     |       |     1 |    26 |    31   (7)| 00:00:01 |
|   3 |    HASH GROUP BY                           |       |     1 |    15 |    31   (7)| 00:00:01 |
|   4 |     NESTED LOOPS                           |       |     1 |    15 |    30   (4)| 00:00:01 |
|   5 |      SORT UNIQUE                           |       |     1 |     2 |    29   (0)| 00:00:01 |
|   6 |       COLLECTION ITERATOR CONSTRUCTOR FETCH|       |     1 |     2 |    29   (0)| 00:00:01 |
|*  7 |      INDEX RANGE SCAN                      | IDX_D |     1 |    13 |     0   (0)|          |
|*  8 |   INDEX UNIQUE SCAN                        | PK_M  |     1 |    13 |     0   (0)|          |
----------------------------------------------------------------------------------------------------

問題の述語を派生テーブルに手動でプッシュダウンしました。 CBOのこの機能の欠如を、期待どおりに機能しない予想されるプッシュダウン操作のリストに追加します(UNIONを使用して派生テーブルに述語をプッシュダウンするなど)。

オラクル-その謎がその力によってのみ超えられるデバイス!

1
Lukas Eder

ここに何か問題があるようです:インデックスフルスキャンの0コストは疑わしいものであり、私が推測しなければならない場合、何か不足していると言えます:おそらくインデックスの統計です。これにより、オプティマイザはFULL INDEX SCANを「無料」で実行できると信じ、次善の計画を続行します。

データが非常に少ないため、これは丸めエラーの問題である可能性もあります(1kの小さな行、おそらく1つのブロックに収まります!)。したがって、いくつかの統計が欠落しているか、意味のあるデータが少なすぎるかのいずれかです。興味深いことに、大きなサンプル(たとえば100万行)を使用してテストを実行する場合、オプティマイザはインデックススキャンを実行します。

代わりにいくつかのデータを挿入し、標準的な統計分析を行うと、より論理的な計画(11.2.0.3)が見つかります。

SQL> INSERT INTO m (SELECT ROWNUM FROM dual CONNECT BY LEVEL <= 1e3);
1000 rows inserted
SQL> INSERT INTO d (SELECT ceil(ROWNUM/10) FROM dual CONNECT BY LEVEL <= 1e4);
10000 rows inserted
SQL> BEGIN
  2     dbms_stats.gather_table_stats(USER, 'M', CASCADE => TRUE,
  3        method_opt =>'FOR ALL INDEXED COLUMNS SIZE 1' );
  4     dbms_stats.gather_table_stats(USER, 'D', CASCADE => TRUE);
  5  END;
  6  /
PL/SQL procedure successfully completed
SQL> EXPLAIN PLAN FOR
  2  SELECT m.id, sub.cnt
  3    FROM m
  4    LEFT JOIN (
  5      SELECT d.m_id, COUNT(1) AS cnt -- (+ other functions)
  6        FROM d
  7       GROUP BY d.m_id
  8    ) sub ON ( sub.m_id = m.id )
  9   WHERE m.id IN (
 10     SELECT /*+ CARDINALITY(tab,1) */ COLUMN_VALUE
 11       FROM TABLE( NEW t(1) ) tab
 12  );
Explained
SQL> SELECT * FROM table(dbms_xplan.display);
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 2820970863
--------------------------------------------------------------------------------
| Id  | Operation                                | Name | Rows  | Bytes | Cost (
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                         |      |     1 |    32 |    31
|*  1 |  HASH JOIN OUTER                         |      |     1 |    32 |    31
|   2 |   NESTED LOOPS                           |      |     1 |     6 |    22
|   3 |    SORT UNIQUE                           |      |     1 |     2 |    21
|   4 |     COLLECTION ITERATOR CONSTRUCTOR FETCH|      |     1 |     2 |    21
|*  5 |    INDEX UNIQUE SCAN                     | PK_M |     1 |     4 |     0
|   6 |   VIEW                                   |      |  1000 | 26000 |     8
|   7 |    HASH GROUP BY                         |      |  1000 |  4000 |     8
|   8 |     TABLE ACCESS FULL                    | D    | 10000 | 40000 |     6
--------------------------------------------------------------------------------
0
Vincent Malgrat