web-dev-qa-db-ja.com

さまざまなフィルター句の高速ページ付け結果

私は自分のデータベース(11g)のテーブルからページ分割された結果を取得する作業をしています。機能するクエリがあります(つまり、結果は正しいです)が、期待どおりに機能せず、効率を向上させようとしています(そのクエリで1秒あたり最大60回の呼び出し)。

最初に、私は Oracleのページ数をカウントする効果的な方法は何ですか? を読み、記事も指摘しましたが、残念ながら、提示されたクエリの実行計画についてはまったく触れていません。 (私は彼らが月曜日に来るのを見ることになるでしょう)。

これが私が思いついたクエリです(テーブルは日付範囲であるpart_codeで分割されています):

select <all-columns> from (
    select rownum as rnum, <all-columns> from (
        select /*+index (my_table index)*/ <all-columns> from my_table
        where part_code in (?, ?, ?)
          and date between ? and ?
          and type in (?, ?, ?)
          and func_col1 = ?
          /*potentially: and func_col2 = ? and func_col3 = ? ... */
        order by date, type, id
     )
) where rnum between M and N; /* N-M ~= 30 */

注:ほとんどのクエリは単一のfunc_xxxフィルターを使用して実行され、Mは小さいと思いますが、保証はありません。理論的には、Mは9999まで可能です。

注:合計で約72パーティションですが、最大で約39のみがアクティブで、func_col1の値は約300,000です(ただし、一部は50,000行、一部は1行です)、typeidの最大値は10です。 (シーケンスによって生成されます)。

これは機能しますが、実行計画には厄介な驚きがあります。2番目のクエリ(rownum as rnumを使用)は完全に実行され、ページ分割が始まる前にDBから最大50,000行をフェッチします。最大50,000行をフェッチします。 DBからそれらの約30を返すには、特に非効率的です。

実行計画では、これは次のように表示されます。

View
\_ Filter (rnum)
   \_ View <= here comes the trouble
      \_ Sort
         \_ ...

必要に応じてテーブルにインデックスを作成し、既存のパーティションインデックス(part_code, func_col1, date, type, id)を変換できます。インデックスは、私のorder by句で要求される順序とまったく同じです。しかし、それは十分ではないように思われます(そして、order by句を削除しても、とにかく近づくことはありません)。

外部クエリがより多くのデータを必要とする(つまり、移動する)ビューの「具体化」を防止し、Oracleがオンザフライで作成する方法はありますか?内部クエリの遅延評価へ)?

3
Matthieu M.

実際のソート操作を回避し、できるだけ早く「停止」する計画を目指すべきだと思います。

並べ替え(および内部ビューの "具体化")を回避するには、並べ替え順序がインデックス列と正確に一致するか、またはwhere句がすべての先行列でのみ厳密に等しい必要があります。それ以外の場合は、サブセットをソートする必要があり、内部ビュー全体を評価する必要があります。

以下は、架空のfooテーブルの例です。

create table foo (a number, b number, c varchar(100));
-- fill with dummy data
exec dbms_stats.gather_table_stats(ownname => user, tabname => 'FOO');
create index foo_ix on foo(a, b, c);

あなたの選択に最も近い単純なもの:

select * from (
  select rownum r, i.* from (
    select a, b, c
    from foo
    where a in (3,4) and b = 3
    order by c
  )  i
) where r between 5 and 10;

説明計画は良くありません:

--------------------------------------------------------------------------------
| Id  | Operation             | Name   | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |        |   163 | 14833 |     5  (20)| 00:00:01 |
|*  1 |  VIEW                 |        |   163 | 14833 |     5  (20)| 00:00:01 |
|   2 |   COUNT               |        |       |       |            |          |
|   3 |    VIEW               |        |   163 | 12714 |     5  (20)| 00:00:01 |
|   4 |     SORT ORDER BY     |        |   163 |  9291 |     5  (20)| 00:00:01 |
|   5 |      INLIST ITERATOR  |        |       |       |            |          |
|*  6 |       INDEX RANGE SCAN| FOO_IX |   163 |  9291 |     4   (0)| 00:00:01 |
--------------------------------------------------------------------------------

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

   1 - filter("R"<=10 AND "R">=5)
   6 - access(("A"=3 OR "A"=4) AND "B"=3)

カウントは遅すぎ、この場合、実際には(物事を「止める」というわけではありません)。

インデックス列を注文に追加します(要件を満たしていない可能性があります)。

select * from (
  select rownum r, i.* from (
    select a, b, c
    from foo
    where a in (3,4) and b = 3
    order by a, b, c
  )  i
) where r between 5 and 10;
-------------------------------------------------------------------------------
| Id  | Operation            | Name   | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |        |   163 | 14833 |     4   (0)| 00:00:01 |
|*  1 |  VIEW                |        |   163 | 14833 |     4   (0)| 00:00:01 |
|   2 |   COUNT              |        |       |       |            |          |
|   3 |    VIEW              |        |   163 | 12714 |     4   (0)| 00:00:01 |
|   4 |     INLIST ITERATOR  |        |       |       |            |          |
|*  5 |      INDEX RANGE SCAN| FOO_IX |   163 |  9291 |     4   (0)| 00:00:01 |
-------------------------------------------------------------------------------

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

   1 - filter("R"<=10 AND "R">=5)
   5 - access(("A"=3 OR "A"=4) AND "B"=3)

少なくとも、私たちはその種類を取り除きました。さあ、試してみて、「やめて」:

select * from (
  select rownum r, i.* from (
    select a, b, c
    from foo
    where a in (3,4) and b = 3
    order by a, b, c
  )  i where rownum < 10
) where r > 5;
-------------------------------------------------------------------------------
| Id  | Operation            | Name   | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |        |     9 |   819 |     4   (0)| 00:00:01 |
|*  1 |  VIEW                |        |     9 |   819 |     4   (0)| 00:00:01 |
|*  2 |   COUNT STOPKEY      |        |       |       |            |          |
|   3 |    VIEW              |        |     9 |   702 |     4   (0)| 00:00:01 |
|   4 |     INLIST ITERATOR  |        |       |       |            |          |
|*  5 |      INDEX RANGE SCAN| FOO_IX |     9 |   513 |     4   (0)| 00:00:01 |
-------------------------------------------------------------------------------

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

   1 - filter("R">5)
   2 - filter(ROWNUM<10)
   5 - access(("A"=3 OR "A"=4) AND "B"=3)

これはあなたのクエリには十分かもしれません:count stopkeyrownum <マジック、私が見たものではbetweenでキックしない)と「行」列に注意してください-内部スキャンはしませんNが見つかった後、さらに行をフェッチすることを気にする必要があります。

上記を使用しても、テーブルからN行を読み取ることができます。

ifすべての検索条件にインデックスを付けることを制限できます。上記のフィルタリングを実行しますが、すべての列ではなく各一致からROWIDのみを取得し、ROWIDでテーブルにアクセスします。

select * from foo where rowid in (
 select rid from (
    select rownum r, i.* from (
      select rowid rid
      from foo
      where a in (3,4) and b = 3
      order by a, b, c
    )  i where rownum < 10
  ) where r > 5
);
----------------------------------------------------------------------------------------
| Id  | Operation                   | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |          |     1 |    78 |     6  (17)| 00:00:01 |
|   1 |  NESTED LOOPS               |          |     1 |    78 |     6  (17)| 00:00:01 |
|   2 |   VIEW                      | VW_NSO_1 |     9 |   108 |     4   (0)| 00:00:01 |
|   3 |    HASH UNIQUE              |          |     1 |   225 |            |          |
|*  4 |     VIEW                    |          |     9 |   225 |     4   (0)| 00:00:01 |
|*  5 |      COUNT STOPKEY          |          |       |       |            |          |
|   6 |       VIEW                  |          |     9 |   108 |     4   (0)| 00:00:01 |
|   7 |        INLIST ITERATOR      |          |       |       |            |          |
|*  8 |         INDEX RANGE SCAN    | FOO_IX   |     9 |   594 |     4   (0)| 00:00:01 |
|   9 |   TABLE ACCESS BY USER ROWID| FOO      |     1 |    66 |     1   (0)| 00:00:01 |
----------------------------------------------------------------------------------------

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

   4 - filter("R">5)
   5 - filter(ROWNUM<10)
   8 - access(("A"=3 OR "A"=4) AND "B"=3)

このしないは、検索フィールドのいずれかがインデックスにない場合に機能します。そして、実際のデータと通常の検索基準でこれをトレースして、実質的に違いがあるかどうかを確認してください。特に、Mの低い値(おそらく、最速にしたい場合)またはN-M

5
Mat