24

次のパーティション テーブルがあります。

CREATE TABLE "ERMB_LOG_TEST_BF"."OUT_SMS"(
    "TRX_ID" NUMBER(19,0) NOT NULL ENABLE,
    "CREATE_TS" TIMESTAMP (3) DEFAULT systimestamp NOT NULL ENABLE,
    /* other fields... */
) PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255
  STORAGE(BUFFER_POOL DEFAULT FLASH_CACHE DEFAULT CELL_FLASH_CACHE DEFAULT)
  TABLESPACE "ERMB_LOG_TEST_BF"
  PARTITION BY RANGE ("TRX_ID") INTERVAL (281474976710656)
  (PARTITION "SYS_P1358"  VALUES LESS THAN (59109745109237760) SEGMENT CREATION IMMEDIATE
  PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255
  NOCOMPRESS LOGGING
  STORAGE(INITIAL 8388608 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
  PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1
  BUFFER_POOL DEFAULT FLASH_CACHE DEFAULT CELL_FLASH_CACHE DEFAULT)
  TABLESPACE "ERMB_LOG_TEST_BF");

CREATE INDEX "ERMB_LOG_TEST_BF"."OUT_SMS_CREATE_TS_TRX_ID_IX" ON "ERMB_LOG_TEST_BF"."OUT_SMS" ("CREATE_TS" DESC, "TRX_ID" DESC)
    PCTFREE 10 INITRANS 2 MAXTRANS 255
    STORAGE(
    BUFFER_POOL DEFAULT FLASH_CACHE DEFAULT CELL_FLASH_CACHE DEFAULT) LOCAL
    (PARTITION "SYS_P1358"
    PCTFREE 10 INITRANS 2 MAXTRANS 255 LOGGING
    STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
    PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1
    BUFFER_POOL DEFAULT FLASH_CACHE DEFAULT CELL_FLASH_CACHE DEFAULT)
    TABLESPACE "ERMB_LOG_TEST_BF");

日付とトランザクションで並べ替えられた 20 のレコードを選択する SQL クエリがあります。

select rd from (
    select /*+ INDEX(OUT_SMS OUT_SMS_CREATE_TS_TRX_ID_IX) */ rowid rd
    from OUT_SMS     
    where  TRX_ID between 34621422135410688 and 72339069014638591       
       and CREATE_TS between to_timestamp('2013-02-01 00:00:00', 'yyyy-mm-dd hh24:mi:ss') 
                         and to_timestamp('2013-03-06 08:57:00', 'yyyy-mm-dd hh24:mi:ss')       
    order by CREATE_TS DESC, TRX_ID DESC
) where rownum <= 20

オラクルは次の計画を生成しました:

    -----------------------------------------------------------------------------------------------------------------------------------
    | Id  | Operation                   | Name                        | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     | Pstart| Pstop |
    -----------------------------------------------------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT            |                             |    20 |   240 |       |  4788K  (1)| 00:05:02 |       |       |
    |*  1 |  COUNT STOPKEY              |                             |       |       |       |            |          |       |       |
    |   2 |   VIEW                      |                             |   312M|  3576M|       |  4788K  (1)| 00:05:02 |       |       |
    |*  3 |    SORT ORDER BY STOPKEY    |                             |   312M|     9G|    12G|  4788K  (1)| 00:05:02 |       |       |
    |   4 |     PARTITION RANGE ITERATOR|                             |   312M|     9G|       |    19   (0)| 00:00:01 |     1 |    48 |
    |*  5 |      COUNT STOPKEY          |                             |       |       |       |            |          |       |       |
    |*  6 |       INDEX RANGE SCAN      | OUT_SMS_CREATE_TS_TRX_ID_IX |   312M|     9G|       |    19   (0)| 00:00:01 |     1 |    48 |
    -----------------------------------------------------------------------------------------------------------------------------------

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

    1 - filter(ROWNUM<=20)
    3 - filter(ROWNUM<=20)
    5 - filter(ROWNUM<=20)
    6 - access(SYS_OP_DESCEND("CREATE_TS")>=HEXTORAW('878EFCF9F6C5FEFAFF')  AND
    SYS_OP_DESCEND("TRX_ID")>=HEXTORAW('36F7E7D7F8A4F0BFA9A3FF')  AND
    SYS_OP_DESCEND("CREATE_TS")<=HEXTORAW('878EFDFEF8FEF8FF')  AND
    SYS_OP_DESCEND("TRX_ID")<=HEXTORAW('36FBD0E9D4E9DBD5F8A6FF') )
    filter(SYS_OP_UNDESCEND(SYS_OP_DESCEND("CREATE_TS"))<=TIMESTAMP' 2013-03-06 08:57:00,000000000' AND
    SYS_OP_UNDESCEND(SYS_OP_DESCEND("TRX_ID"))<=72339069014638591 AND
    SYS_OP_UNDESCEND(SYS_OP_DESCEND("TRX_ID"))>=34621422135410688 AND
    SYS_OP_UNDESCEND(SYS_OP_DESCEND("CREATE_TS"))>=TIMESTAMP' 2013-02-01 00:00:00,000000000')

それは完全に機能します。

ちなみに、テーブルOUT_SMSはフィールドごとに分割されており、各パーティションのローカルインデックスです。TRX_IDOUT_SMS_CREATE_TS_TRX_ID_IX (CREATE_TS DESC, TRX_ID DESC)

しかし、このクエリを準備済みステートメントに変換すると:

select rd from (
    select /*+ INDEX(OUT_SMS OUT_SMS_CREATE_TS_TRX_ID_IX) */ rowid rd
    from OUT_SMS     
    where  TRX_ID between ? and ?       
       and CREATE_TS between ? and ?
    order by CREATE_TS DESC, TRX_ID DESC
) where rownum <= 20

Oracle は次の計画を生成します。

    ----------------------------------------------------------------------------------------------------------------------------
    | Id  | Operation                    | Name                        | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
    ----------------------------------------------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT             |                             |    20 |   240 | 14743   (1)| 00:00:01 |       |       |
    |*  1 |  COUNT STOPKEY               |                             |       |       |            |          |       |       |
    |   2 |   VIEW                       |                             |  1964 | 23568 | 14743   (1)| 00:00:01 |       |       |
    |*  3 |    SORT ORDER BY STOPKEY     |                             |  1964 | 66776 | 14743   (1)| 00:00:01 |       |       |
    |*  4 |     FILTER                   |                             |       |       |            |          |       |       |
    |   5 |      PARTITION RANGE ITERATOR|                             |  1964 | 66776 | 14742   (1)| 00:00:01 |   KEY |   KEY |
    |*  6 |       INDEX RANGE SCAN       | OUT_SMS_CREATE_TS_TRX_ID_IX |  1964 | 66776 | 14742   (1)| 00:00:01 |   KEY |   KEY |
    ----------------------------------------------------------------------------------------------------------------------------

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

    1 - filter(ROWNUM<=20)
    3 - filter(ROWNUM<=20)
    4 - filter(TO_TIMESTAMP(:RR,'yyyy-mm-dd hh24:mi:ss')<=TO_TIMESTAMP(:T,'yyyy-mm-dd hh24:mi:ss') AND
    TO_NUMBER(:ABC)<=TO_NUMBER(:EBC))
    6 - access(SYS_OP_DESCEND("CREATE_TS")>=SYS_OP_DESCEND(TO_TIMESTAMP(:T,'yyyy-mm-dd hh24:mi:ss')) AND
    SYS_OP_DESCEND("TRX_ID")>=SYS_OP_DESCEND(TO_NUMBER(:EBC)) AND
    SYS_OP_DESCEND("CREATE_TS")<=SYS_OP_DESCEND(TO_TIMESTAMP(:RR,'yyyy-mm-dd hh24:mi:ss')) AND
    SYS_OP_DESCEND("TRX_ID")<=SYS_OP_DESCEND(TO_NUMBER(:ABC)))
    filter(SYS_OP_UNDESCEND(SYS_OP_DESCEND("TRX_ID"))>=TO_NUMBER(:ABC) AND
    SYS_OP_UNDESCEND(SYS_OP_DESCEND("TRX_ID"))<=TO_NUMBER(:EBC) AND
    SYS_OP_UNDESCEND(SYS_OP_DESCEND("CREATE_TS"))>=TO_TIMESTAMP(:RR,'yyyy-mm-dd hh24:mi:ss') AND
    SYS_OP_UNDESCEND(SYS_OP_DESCEND("CREATE_TS"))<=TO_TIMESTAMP(:T,'yyyy-mm-dd hh24:mi:ss'))

オペレーションCOUNT STOPKEYは計画から消えます。この操作は、最初のクエリのように、各パーティションから 20 行を取得するためにインデックスが分析された後に行う必要があります。

プランに COUNT STOPKEY を含めるように準備済みステートメントを作成するにはどうすればよいですか?

4

4 に答える 4

10

バインド変数を使用すると、Oracle は静的パーティションのプルーニングではなく動的パーティションのプルーニングを使用するように強制されます。この結果、Oracle は解析時にアクセスされるパーティションを認識できません。これは、入力変数に基づいて変化するためです。

これは、(バインド変数の代わりに) リテラル値を使用する場合、ローカル インデックスによってアクセスされるパーティションがわかるということです。したがって、count stopkeyパーティションをプルーニングする前に、インデックスの出力に適用できます。

バインド変数を使用する場合、partition range iteratorアクセスしているパーティションを把握する必要があります。次に、操作間の最初の変数が実際には 2 番目の変数 ( filter2 番目の計画の操作) よりも低い値であることを確認するためのチェックがあります。

次のテスト ケースが示すように、これは簡単に再現できます。

create table tab (
  x date,
  y integer,
  filler varchar2(100)
) partition by range(x) (
  partition p1 values less than (date'2013-01-01'),
  partition p2 values less than (date'2013-02-01'),
  partition p3 values less than (date'2013-03-01'),
  partition p4 values less than (date'2013-04-01'),
  partition p5 values less than (date'2013-05-01'),
  partition p6 values less than (date'2013-06-01')
);


insert into tab (x, y)
  select add_months(trunc(sysdate, 'y'), mod(rownum, 5)), rownum, dbms_random.string('x', 50)
  from   dual 
  connect by level <= 1000;

create index i on tab(x desc, y desc) local;

exec dbms_stats.gather_table_stats(user, 'tab', cascade => true);

explain plan for 
SELECT * FROM (
  SELECT rowid FROM tab
  where  x between date'2013-01-01' and date'2013-02-02'
  and    y between 50 and 100
  order  by x desc, y desc
)
where rownum <= 5;

SELECT * FROM table(dbms_xplan.display(null, null, 'BASIC +ROWS +PARTITION'));

--------------------------------------------------------------------                                                                                                                                                                                                                                         
| Id  | Operation                   | Name | Rows  | Pstart| Pstop |                                                                                                                                                                                                                                         
--------------------------------------------------------------------                                                                                                                                                                                                                                         
|   0 | SELECT STATEMENT            |      |     1 |       |       |                                                                                                                                                                                                                                         
|   1 |  COUNT STOPKEY              |      |       |       |       |                                                                                                                                                                                                                                         
|   2 |   VIEW                      |      |     1 |       |       |                                                                                                                                                                                                                                         
|   3 |    SORT ORDER BY STOPKEY    |      |     1 |       |       |                                                                                                                                                                                                                                         
|   4 |     PARTITION RANGE ITERATOR|      |     1 |     2 |     3 |                                                                                                                                                                                                                                         
|   5 |      COUNT STOPKEY          |      |       |       |       |                                                                                                                                                                                                                                         
|   6 |       INDEX RANGE SCAN      | I    |     1 |     2 |     3 |                                                                                                                                                                                                                                         
-------------------------------------------------------------------- 

explain plan for 
SELECT * FROM (
  SELECT rowid FROM tab
  where  x between to_date(:st, 'dd/mm/yyyy') and to_date(:en, 'dd/mm/yyyy')
  and    y between :a and :b
  order  by x desc, y desc
)
where rownum <= 5;

SELECT * FROM table(dbms_xplan.display(null, null, 'BASIC +ROWS +PARTITION'));

---------------------------------------------------------------------                                                                                                                                                                                                                                        
| Id  | Operation                    | Name | Rows  | Pstart| Pstop |                                                                                                                                                                                                                                        
---------------------------------------------------------------------                                                                                                                                                                                                                                        
|   0 | SELECT STATEMENT             |      |     1 |       |       |                                                                                                                                                                                                                                        
|   1 |  COUNT STOPKEY               |      |       |       |       |                                                                                                                                                                                                                                        
|   2 |   VIEW                       |      |     1 |       |       |                                                                                                                                                                                                                                        
|   3 |    SORT ORDER BY STOPKEY     |      |     1 |       |       |                                                                                                                                                                                                                                        
|   4 |     FILTER                   |      |       |       |       |                                                                                                                                                                                                                                        
|   5 |      PARTITION RANGE ITERATOR|      |     1 |   KEY |   KEY |                                                                                                                                                                                                                                        
|   6 |       INDEX RANGE SCAN       | I    |     1 |   KEY |   KEY |                                                                                                                                                                                                                                        
--------------------------------------------------------------------- 

keyあなたの例のように、2番目のクエリは、最初の例のように正確なパーティションではなく、解析時にのみパーティションをフィルタリングできます。

これは、リテラル値がバインド変数よりも優れたパフォーマンスを提供できるまれなケースの 1 つです。これが可能性があるかどうかを調査する必要があります。

最後に、各パーティションから 20 行が必要だとします。そのままのクエリではこれは実行されません。順序に従って最初の 20 行が返されるだけです。20 行/パーティションの場合、次のようにする必要があります。

select rd from (
    select rowid rd, 
           row_number() over (partition by trx_id order by create_ts desc) rn
    from OUT_SMS     
    where  TRX_ID between ? and ?       
       and CREATE_TS between ? and ?
    order by CREATE_TS DESC, TRX_ID DESC
) where rn <= 20

アップデート

が得られない理由は、「悪い」計画の 4 行目count stopkeyの操作に関係しています。filter上記の例をパーティション分割なしで繰り返すと、これをより明確に確認できます。

これにより、次の計画が得られます。

----------------------------------------                                                                                                                                                                                                                                                                     
| Id  | Operation               | Name |                                                                                                                                                                                                                                                                     
----------------------------------------                                                                                                                                                                                                                                                                     
|   0 | SELECT STATEMENT        |      |                                                                                                                                                                                                                                                                     
|*  1 |  COUNT STOPKEY          |      |                                                                                                                                                                                                                                                                     
|   2 |   VIEW                  |      |                                                                                                                                                                                                                                                                     
|*  3 |    SORT ORDER BY STOPKEY|      |                                                                                                                                                                                                                                                                     
|*  4 |     TABLE ACCESS FULL   | TAB  |                                                                                                                                                                                                                                                                     
----------------------------------------                                                                                                                                                                                                                                                                     

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

   1 - filter(ROWNUM<=5)                                                                                                                                                                                                                                                                                     
   3 - filter(ROWNUM<=5)                                                                                                                                                                                                                                                                                     
   4 - filter("X">=TO_DATE(' 2013-01-01 00:00:00', 'syyyy-mm-dd                                                                                                                                                                                                                                              
              hh24:mi:ss') AND "X"<=TO_DATE(' 2013-02-02 00:00:00', 'syyyy-mm-dd                                                                                                                                                                                                                             
              hh24:mi:ss') AND "Y">=50 AND "Y"<=100)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       

----------------------------------------                                                                                                                                                                                                                                                                     
| Id  | Operation               | Name |                                                                                                                                                                                                                                                                     
----------------------------------------                                                                                                                                                                                                                                                                     
|   0 | SELECT STATEMENT        |      |                                                                                                                                                                                                                                                                     
|*  1 |  COUNT STOPKEY          |      |                                                                                                                                                                                                                                                                     
|   2 |   VIEW                  |      |                                                                                                                                                                                                                                                                     
|*  3 |    SORT ORDER BY STOPKEY|      |                                                                                                                                                                                                                                                                     
|*  4 |     FILTER              |      |                                                                                                                                                                                                                                                                     
|*  5 |      TABLE ACCESS FULL  | TAB  |                                                                                                                                                                                                                                                                     
----------------------------------------                                                                                                                                                                                                                                                                     

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

   1 - filter(ROWNUM<=5)                                                                                                                                                                                                                                                                                     
   3 - filter(ROWNUM<=5)                                                                                                                                                                                                                                                                                     
   4 - filter(TO_NUMBER(:A)<=TO_NUMBER(:B) AND                                                                                                                                                                                                                                                               
              TO_DATE(:ST,'dd/mm/yyyy')<=TO_DATE(:EN,'dd/mm/yyyy'))                                                                                                                                                                                                                                          
   5 - filter("Y">=TO_NUMBER(:A) AND "Y"<=TO_NUMBER(:B) AND                                                                                                                                                                                                                                                  
              "X">=TO_DATE(:ST,'dd/mm/yyyy') AND "X"<=TO_DATE(:EN,'dd/mm/yyyy'))   

ご覧のとおりfiltersort order by stopkey. これは、インデックスにアクセスした後に発生します。これは、変数の値がデータを返すことを許可することを確認しています (その間の最初の変数は、実際には 2 番目の変数よりも低い値を持っています)。リテラルを使用する場合、オプティマイザーは 50 が 100 未満であることを既に認識しているため (この場合)、これは必要ありません。ただし、解析時に :a が :b より小さいかどうかはわかりません。

なぜこれが正確なのかわかりません。これは、オラクルによる意図的な設計である可能性があります。変数に設定された値がゼロ行になる場合、ストップキーチェックを行う意味はありません。または単なる見落としです。

于 2013-03-18T17:48:41.377 に答える
8

11.2.0.3 で調査結果を再現できます。これが私のテストケースです:

SQL> -- Table with 100 partitions of 100 rows 
SQL> CREATE TABLE out_sms
  2  PARTITION BY RANGE (trx_id)
  3     INTERVAL (100) (PARTITION p0 VALUES LESS THAN (0))
  4  AS
  5  SELECT ROWNUM trx_id,
  6         trunc(SYSDATE) + MOD(ROWNUM, 50) create_ts
  7  FROM dual CONNECT BY LEVEL <= 10000;

Table created

SQL> CREATE INDEX OUT_SMS_IDX ON out_sms (create_ts desc, trx_id desc) LOCAL;

Index created

[static plan]

SELECT rd
  FROM (SELECT /*+ INDEX(OUT_SMS OUT_SMS_IDX) */
         rowid rd
          FROM out_sms
         WHERE create_ts BETWEEN systimestamp AND systimestamp + 10
           AND trx_id BETWEEN 1 AND 500
         ORDER BY create_ts DESC, trx_id DESC)
 WHERE rownum <= 20;    
---------------------------------------------------------------------------
| Id  | Operation                   | Name        | Rows  | Pstart| Pstop |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |             |     1 |       |       |
|*  1 |  COUNT STOPKEY              |             |       |       |       |
|   2 |   VIEW                      |             |     1 |       |       |
|*  3 |    SORT ORDER BY STOPKEY    |             |     1 |       |       |
|   4 |     PARTITION RANGE ITERATOR|             |     1 |     2 |     7 |
|*  5 |      COUNT STOPKEY          |             |       |       |       |
|*  6 |       INDEX RANGE SCAN      | OUT_SMS_IDX |     1 |     2 |     7 |
---------------------------------------------------------------------------

[dynamic]     
----------------------------------------------------------------------------
| Id  | Operation                    | Name        | Rows  | Pstart| Pstop |
----------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |             |     1 |       |       |
|*  1 |  COUNT STOPKEY               |             |       |       |       |
|   2 |   VIEW                       |             |     1 |       |       |
|*  3 |    SORT ORDER BY STOPKEY     |             |     1 |       |       |
|*  4 |     FILTER                   |             |       |       |       |
|   5 |      PARTITION RANGE ITERATOR|             |     1 |   KEY |   KEY |
|*  6 |       INDEX RANGE SCAN       | OUT_SMS_IDX |     1 |   KEY |   KEY |
----------------------------------------------------------------------------

あなたの例のように、2番目のケースではなく、最初のケースで述語がパーティションインデックス範囲スキャンROWNUMにプッシュされます。静的変数を使用する場合、プランは、Oracle がパーティションごとに 20 行のみをフェッチすることを示していますが、動的変数を使用すると、Oracle は各パーティションで句を満たすすべての行をフェッチします。バインド変数の使用時に述語をプッシュできる設定または統計構成が見つかりませんでした。WHERE

システムをゲームするために、より広い静的制限を持つ動的フィルターを使用できることを望んでいましたROWNUMが、動的変数が存在するとすぐに、個々のパーティション内で述語が使用されないようです。

SELECT rd
  FROM (SELECT /*+ INDEX(OUT_SMS OUT_SMS_IDX) */
         rowid rd
          FROM out_sms
         WHERE nvl(create_ts+:5, sysdate) BETWEEN :1 AND :2
           AND nvl(trx_id+:6, 0) BETWEEN :3 AND :4
           AND trx_id BETWEEN 1 AND 500
           AND create_ts BETWEEN systimestamp AND systimestamp + 10
         ORDER BY create_ts DESC, trx_id DESC)
 WHERE rownum <= 20

Plan hash value: 2740263591

----------------------------------------------------------------------------
| Id  | Operation                    | Name        | Rows  | Pstart| Pstop |
----------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |             |     1 |       |       |
|*  1 |  COUNT STOPKEY               |             |       |       |       |
|   2 |   VIEW                       |             |     1 |       |       |
|*  3 |    SORT ORDER BY STOPKEY     |             |     1 |       |       |
|*  4 |     FILTER                   |             |       |       |       |
|   5 |      PARTITION RANGE ITERATOR|             |     1 |     2 |     7 |
|*  6 |       INDEX RANGE SCAN       | OUT_SMS_IDX |     1 |     2 |     7 |
----------------------------------------------------------------------------

このクエリが重要で、そのパフォーマンスが重要な場合は、インデックスをグローバル インデックスに変換できます。これにより、パーティションのメンテナンスが増加しますが、ほとんどのパーティション操作は、最近の Oracle バージョンでオンラインで使用できます。この場合、グローバル インデックスは標準の非パーティション テーブルと同じように機能します。

SQL> drop index out_sms_idx;

Index dropped

SQL> CREATE INDEX OUT_SMS_IDX ON out_sms (create_ts DESC, trx_id desc);

Index created

SELECT rd
  FROM (SELECT 
         rowid rd
          FROM out_sms
         WHERE create_ts BETWEEN :1 AND :2
           AND trx_id BETWEEN :3 AND :4
         ORDER BY create_ts DESC, trx_id DESC)
 WHERE rownum <= 20

------------------------------------------------------------------------
| Id  | Operation           | Name        | Rows  | Bytes | Cost (%CPU)|
------------------------------------------------------------------------
|   0 | SELECT STATEMENT    |             |     1 |    12 |     2   (0)|
|*  1 |  COUNT STOPKEY      |             |       |       |            |
|   2 |   VIEW              |             |     1 |    12 |     2   (0)|
|*  3 |    FILTER           |             |       |       |            |
|*  4 |     INDEX RANGE SCAN| OUT_SMS_IDX |     1 |    34 |     2   (0)|
------------------------------------------------------------------------
于 2013-03-19T10:27:17.093 に答える
2

問題の問題は、Oracle 12.1.0.2.0 でも引き続き問題であることを確認できます。

また、ハードコーディングされたパーティション削除境界でさえ十分ではありません。

私の場合のテストテーブルは次のとおりです。

CREATE TABLE FR_MESSAGE_PART (
    ID NUMBER(38) NOT NULL CONSTRAINT PK_FR_MESSAGE_PART PRIMARY KEY USING INDEX LOCAL,
    TRX_ID NUMBER(38) NOT NULL, TS TIMESTAMP NOT NULL, TEXT CLOB)
    PARTITION BY RANGE (ID) (PARTITION PART_0 VALUES LESS THAN (0));
CREATE INDEX IX_FR_MESSAGE_PART_TRX_ID ON FR_MESSAGE_PART(TRX_ID) LOCAL;
CREATE INDEX IX_FR_MESSAGE_PART_TS ON FR_MESSAGE_PART(TS) LOCAL;

このテーブルには、数か月分の OLTP 運用データの数百万のレコードが入力されています。各月は個別のパーティションに属します。

IDこのテーブルの主キー値には常に上位ビットに時刻部分が含まれており、暦期間による範囲分割に使用できます。すべてのメッセージは の上位の時間ビットを継承しますTRX_ID。これにより、同じビジネス オペレーションに属するすべてのメッセージが常に同じパーティションに分類されるようになります。

パーティション排除境界が適用された特定の期間の最新メッセージのページを選択するためのハードコーディングされたクエリから始めましょう。

select * from (select * from FR_MESSAGE_PART
where TS >= DATE '2017-11-30' and TS < DATE '2017-12-02'
  and ID >= 376894993815568384 and ID < 411234940974268416
order by TS DESC) where ROWNUM <= 40;

しかし、テーブル統計を新たに収集しても、Oracle オプティマイザは、2 つの月間パーティション全体をソートする方が、既存のローカル インデックスによる 2 日間の範囲スキャンよりも高速であると誤って推定します。

-----------------------------------------------------------------------------------------------------------------------
| Id  | Operation                   | Name            | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     | Pstart| Pstop |
-----------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |                 |    40 | 26200 |       |   103K  (1)| 00:00:05 |       |       |
|*  1 |  COUNT STOPKEY              |                 |       |       |       |            |          |       |       |
|   2 |   VIEW                      |                 |   803K|   501M|       |   103K  (1)| 00:00:05 |       |       |
|*  3 |    SORT ORDER BY STOPKEY    |                 |   803K|    70M|    92M|   103K  (1)| 00:00:05 |       |       |
|   4 |     PARTITION RANGE ITERATOR|                 |   803K|    70M|       | 86382   (1)| 00:00:04 |     2 |     3 |
|*  5 |      TABLE ACCESS FULL      | FR_MESSAGE_PART |   803K|    70M|       | 86382   (1)| 00:00:04 |     2 |     3 |
-----------------------------------------------------------------------------------------------------------------------

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

   1 - filter(ROWNUM<=40)
   3 - filter(ROWNUM<=40)
   5 - filter("TS"<TIMESTAMP' 2017-12-01 00:00:00' AND "TS">=TIMESTAMP' 2017-11-29 00:00:00' AND 
              "ID">=376894993815568384)

実際の実行時間は、計画で見積もったよりも桁違いに長く表示されます。

したがって、インデックスの使用を強制するヒントを適用する必要があります。

select * from (select /*+ FIRST_ROWS(40) INDEX(FR_MESSAGE_PART (TS)) */ * from FR_MESSAGE_PART
where TS >= DATE '2017-11-30' and TS < DATE '2017-12-02'
  and ID >= 376894993815568384 and ID < 411234940974268416
order by TS DESC) where ROWNUM <= 40;

現在、プランはインデックスを使用していますが、2 つのパーティション全体のソートに時間がかかります。

-----------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                                     | Name                  | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     | Pstart| Pstop |
-----------------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                              |                       |    40 | 26200 |       |   615K  (1)| 00:00:25 |       |       |
|*  1 |  COUNT STOPKEY                                |                       |       |       |       |            |          |       |       |
|   2 |   VIEW                                        |                       |   803K|   501M|       |   615K  (1)| 00:00:25 |       |       |
|*  3 |    SORT ORDER BY STOPKEY                      |                       |   803K|    70M|    92M|   615K  (1)| 00:00:25 |       |       |
|   4 |     PARTITION RANGE ITERATOR                  |                       |   803K|    70M|       |   598K  (1)| 00:00:24 |     2 |     3 |
|*  5 |      TABLE ACCESS BY LOCAL INDEX ROWID BATCHED| FR_MESSAGE_PART       |   803K|    70M|       |   598K  (1)| 00:00:24 |     2 |     3 |
|*  6 |       INDEX RANGE SCAN                        | IX_FR_MESSAGE_PART_TS |   576K|       |       |  2269   (1)| 00:00:01 |     2 |     3 |
-----------------------------------------------------------------------------------------------------------------------------------------------

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

   1 - filter(ROWNUM<=40)
   3 - filter(ROWNUM<=40)
   5 - filter("ID">=376894993815568384)
   6 - access("TS">=TIMESTAMP' 2017-11-30 00:00:00' AND "TS"<TIMESTAMP' 2017-12-02 00:00:00')

Oracle ヒント参照とグーグルで苦労した後、 INDEX_DESCまたはINDEX_RS_DESCヒントを使用して、インデックス範囲スキャンの降順を明示的に指定する必要があることがわかりました。

select * from (select /*+ FIRST_ROWS(40) INDEX_RS_DESC(FR_MESSAGE_PART (TS)) */ * from FR_MESSAGE_PART
where TS >= DATE '2017-11-30' and TS < DATE '2017-12-02'
  and ID >= 376894993815568384 and ID < 411234940974268416
order by TS DESC) where ROWNUM <= 40;

COUNT STOPKEYこれにより、パーティションを降順でスキャンし、各パーティションから最大 40 行のみをソートする、パーティションごとの高速プランがついに得られます。

------------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                                      | Name                  | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     | Pstart| Pstop |
------------------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                               |                       |    40 | 26200 |       |   615K  (1)| 00:00:25 |       |       |
|*  1 |  COUNT STOPKEY                                 |                       |       |       |       |            |          |       |       |
|   2 |   VIEW                                         |                       |   803K|   501M|       |   615K  (1)| 00:00:25 |       |       |
|*  3 |    SORT ORDER BY STOPKEY                       |                       |   803K|    70M|    92M|   615K  (1)| 00:00:25 |       |       |
|   4 |     PARTITION RANGE ITERATOR                   |                       |   803K|    70M|       |   598K  (1)| 00:00:24 |     3 |     2 |
|*  5 |      COUNT STOPKEY                             |                       |       |       |       |            |          |       |       |
|*  6 |       TABLE ACCESS BY LOCAL INDEX ROWID BATCHED| FR_MESSAGE_PART       |   803K|    70M|       |   598K  (1)| 00:00:24 |     3 |     2 |
|*  7 |        INDEX RANGE SCAN DESCENDING             | IX_FR_MESSAGE_PART_TS |   576K|       |       |  2269   (1)| 00:00:01 |     3 |     2 |
------------------------------------------------------------------------------------------------------------------------------------------------

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

   1 - filter(ROWNUM<=40)
   3 - filter(ROWNUM<=40)
   5 - filter(ROWNUM<=40)
   6 - filter("ID">=376894993815568384)
   7 - access("TS">=TIMESTAMP' 2017-11-30 00:00:00' AND "TS"<TIMESTAMP' 2017-12-02 00:00:00')
       filter("TS">=TIMESTAMP' 2017-11-30 00:00:00' AND "TS"<TIMESTAMP' 2017-12-02 00:00:00')

これは非常に高速に実行されますが、見積もられたプランのコストは依然として誤って高すぎます。

ここまでは順調ですね。次に、カスタム ORM フレームワークで使用するためにクエリをパラメータ化してみましょう。

select * from (select /*+ FIRST_ROWS(40) INDEX_RS_DESC(FR_MESSAGE_PART (TS)) */ * from FR_MESSAGE_PART
where TS >= :1 and TS < :2
  and ID >= :3 and ID < :4
order by TS DESC) where ROWNUM <= 40;

ただしCOUNT STOPKEY、質問に記載され、他の回答で確認されているように、パーティションごとに計画から消えます。

----------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                                      | Name                  | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
----------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                               |                       |    40 | 26200 | 82349   (1)| 00:00:04 |       |       |
|*  1 |  COUNT STOPKEY                                 |                       |       |       |            |          |       |       |
|   2 |   VIEW                                         |                       |   153 |    97K| 82349   (1)| 00:00:04 |       |       |
|*  3 |    SORT ORDER BY STOPKEY                       |                       |   153 | 14076 | 82349   (1)| 00:00:04 |       |       |
|*  4 |     FILTER                                     |                       |       |       |            |          |       |       |
|   5 |      PARTITION RANGE ITERATOR                  |                       |   153 | 14076 | 82348   (1)| 00:00:04 |   KEY |   KEY |
|*  6 |       TABLE ACCESS BY LOCAL INDEX ROWID BATCHED| FR_MESSAGE_PART       |   153 | 14076 | 82348   (1)| 00:00:04 |   KEY |   KEY |
|*  7 |        INDEX RANGE SCAN DESCENDING             | IX_FR_MESSAGE_PART_TS |   110K|       |   450   (1)| 00:00:01 |   KEY |   KEY |
----------------------------------------------------------------------------------------------------------------------------------------

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

   1 - filter(ROWNUM<=40)
   3 - filter(ROWNUM<=40)
   4 - filter(TO_NUMBER(:4)>TO_NUMBER(:3) AND TO_TIMESTAMP(:2)>TO_TIMESTAMP(:1))
   6 - filter("ID">=TO_NUMBER(:3) AND "ID"<TO_NUMBER(:4))
   7 - access("TS">=TO_TIMESTAMP(:1) AND "TS"<TO_TIMESTAMP(:2))
       filter("TS">=TO_TIMESTAMP(:1) AND "TS"<TO_TIMESTAMP(:2))

次に、ハードコーディングされた月ごとに整列されたパーティションの除外境界に後退しようとしましたが、それでもパラメーター化されたタイムスタンプの境界を保持して、プラン キャッシュのスポイリングを最小限に抑えました。

select * from (select /*+ FIRST_ROWS(40) INDEX_RS_DESC(FR_MESSAGE_PART (TS)) */ * from FR_MESSAGE_PART
where TS >= :1 and TS < :2
  and ID >= 376894993815568384 and ID < 411234940974268416
order by TS DESC) where ROWNUM <= 40;

しかし、まだ遅い計画があります:

------------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                                      | Name                  | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     | Pstart| Pstop |
------------------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                               |                       |    40 | 26200 |       | 83512   (1)| 00:00:04 |       |       |
|*  1 |  COUNT STOPKEY                                 |                       |       |       |       |            |          |       |       |
|   2 |   VIEW                                         |                       | 61238 |    38M|       | 83512   (1)| 00:00:04 |       |       |
|*  3 |    SORT ORDER BY STOPKEY                       |                       | 61238 |  5501K|  7216K| 83512   (1)| 00:00:04 |       |       |
|*  4 |     FILTER                                     |                       |       |       |       |            |          |       |       |
|   5 |      PARTITION RANGE ITERATOR                  |                       | 61238 |  5501K|       | 82214   (1)| 00:00:04 |     3 |     2 |
|*  6 |       TABLE ACCESS BY LOCAL INDEX ROWID BATCHED| FR_MESSAGE_PART       | 61238 |  5501K|       | 82214   (1)| 00:00:04 |     3 |     2 |
|*  7 |        INDEX RANGE SCAN DESCENDING             | IX_FR_MESSAGE_PART_TS | 79076 |       |       |   316   (1)| 00:00:01 |     3 |     2 |
------------------------------------------------------------------------------------------------------------------------------------------------

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

   1 - filter(ROWNUM<=40)
   3 - filter(ROWNUM<=40)
   4 - filter(TO_TIMESTAMP(:2)>TO_TIMESTAMP(:1))
   6 - filter("ID">=376894993815568384)
   7 - access("TS">=TO_TIMESTAMP(:1) AND "TS"<TO_TIMESTAMP(:2))
       filter("TS">=TO_TIMESTAMP(:1) AND "TS"<TO_TIMESTAMP(:2))

ここでの彼の回答で@ChrisSaxonは、ネストされていないことは、上限が下限よりも実際に大きいことを検証する操作とSTOPKEY COUNT関係があると述べています。filter(TO_TIMESTAMP(:2)>TO_TIMESTAMP(:1))

TS between :a and :bこれを考慮して、 equal に変換することでオププリマイザーをごまかそうとしました:b between TS and TS + (:b - :a)。そして、これはうまくいきました!

この変更の根本原因をさらに調査した結果、に置き換えるだけTS >= :1 and TS < :2TS + 0 >= :1 and TS < :2最適な実行計画を達成できることがわかりました。

select * from (select /*+ FIRST_ROWS(40) INDEX_RS_DESC(FR_MESSAGE_PART (TS)) */ * from FR_MESSAGE_PART
where TS + 0 >= :1 and TS < :2
  and ID >= 376894993815568384 and ID < 411234940974268416
order by TS DESC) where ROWNUM <= 40;

計画はCOUNT STOPKEYパーティションごとに適切になり、その概念INTERNAL_FUNCTION("TS")+0により、有毒な余分な境界チェックフィルターが防止されたと思います。

------------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                                      | Name                  | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     | Pstart| Pstop |
------------------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                               |                       |    40 | 26200 |       | 10120   (1)| 00:00:01 |       |       |
|*  1 |  COUNT STOPKEY                                 |                       |       |       |       |            |          |       |       |
|   2 |   VIEW                                         |                       | 61238 |    38M|       | 10120   (1)| 00:00:01 |       |       |
|*  3 |    SORT ORDER BY STOPKEY                       |                       | 61238 |  5501K|  7216K| 10120   (1)| 00:00:01 |       |       |
|   4 |     PARTITION RANGE ITERATOR                   |                       | 61238 |  5501K|       |  8822   (1)| 00:00:01 |     3 |     2 |
|*  5 |      COUNT STOPKEY                             |                       |       |       |       |            |          |       |       |
|*  6 |       TABLE ACCESS BY LOCAL INDEX ROWID BATCHED| FR_MESSAGE_PART       | 61238 |  5501K|       |  8822   (1)| 00:00:01 |     3 |     2 |
|*  7 |        INDEX RANGE SCAN DESCENDING             | IX_FR_MESSAGE_PART_TS |  7908 |       |       |   631   (1)| 00:00:01 |     3 |     2 |
------------------------------------------------------------------------------------------------------------------------------------------------

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

   1 - filter(ROWNUM<=40)
   3 - filter(ROWNUM<=40)
   5 - filter(ROWNUM<=40)
   6 - filter("ID">=376894993815568384)
   7 - access("TS"<TO_TIMESTAMP(:2))
       filter(INTERNAL_FUNCTION("TS")+0>=:1 AND "TS"<TO_TIMESTAMP(:2))

前述の Oracle 固有の+ 0回避策と、カスタム ORM フレームワークでのパーティション削除境界のハードコーディングを実装する必要がありました。これにより、ローカル インデックスを使用して分割されたテーブルに切り替えた後も、同じ高速ページング パフォーマンスを維持できます。

しかし、SQL 構築コードを完全に制御せずに同じ切り替えを敢行しようとする人には、忍耐と正気を祈ります。

パーティショニングとページングが混在している場合、Oracle には多くの落とし穴があるようです。たとえば、Oracle 12 の新しいOFFSET ROWS / FETCH NEXT ROWS ONLY シンタックス シュガーは、ほとんどの分析ウィンドウ関数が基づいているため、ローカルのインデックス付きパーティション テーブルではほとんど使用できないことがわかりました。

最初のページの後ろにあるページを取得するための最短の作業クエリは次のとおりです。

select * from (select * from (
    select /*+ FIRST_ROWS(200) INDEX_RS_DESC(FR_MESSAGE_PART (TS)) */* from FR_MESSAGE_PART
where TS + 0 >= :1 and TS < :2
  and ID >= 376894993815568384 and ID < 411234940974268416
order by TS DESC) where ROWNUM <= 200) offset 180 rows;

このようなクエリを実行した後の実際の実行計画の例を次に示します。

SQL_ID  c67mmq4wg49sx, child number 0
-------------------------------------
select * from (select * from (select /*+ FIRST_ROWS(200)
INDEX_RS_DESC("FR_MESSAGE_PART" ("TS")) GATHER_PLAN_STATISTICS */ "ID",
"MESSAGE_TYPE_ID", "TS", "REMOTE_ADDRESS", "TRX_ID",
"PROTOCOL_MESSAGE_ID", "MESSAGE_DATA_ID", "TEXT_OFFSET", "TEXT_SIZE",
"BODY_OFFSET", "BODY_SIZE", "INCOMING" from "FR_MESSAGE_PART" where
"TS" + 0 >= :1 and "TS" < :2 and "ID" >= 376894993815568384 and "ID" <
411234940974268416 order by "TS" DESC) where ROWNUM <= 200) offset 180
rows

Plan hash value: 2499404919

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                                 | Name                  | Starts | E-Rows |E-Bytes|E-Temp | Cost (%CPU)| E-Time   | Pstart| Pstop | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                          |                       |      1 |        |       |       |   640K(100)|          |       |       |     20 |00:00:00.01 |     322 |       |       |          |
|*  1 |  VIEW                                     |                       |      1 |    200 |   130K|       |   640K  (1)| 00:00:26 |       |       |     20 |00:00:00.01 |     322 |       |       |          |
|   2 |   WINDOW NOSORT                           |                       |      1 |    200 |   127K|       |   640K  (1)| 00:00:26 |       |       |    200 |00:00:00.01 |     322 |   142K|   142K|          |
|   3 |    VIEW                                   |                       |      1 |    200 |   127K|       |   640K  (1)| 00:00:26 |       |       |    200 |00:00:00.01 |     322 |       |       |          |
|*  4 |     COUNT STOPKEY                         |                       |      1 |        |       |       |            |          |       |       |    200 |00:00:00.01 |     322 |       |       |          |
|   5 |      VIEW                                 |                       |      1 |    780K|   487M|       |   640K  (1)| 00:00:26 |       |       |    200 |00:00:00.01 |     322 |       |       |          |
|*  6 |       SORT ORDER BY STOPKEY               |                       |      1 |    780K|    68M|    89M|   640K  (1)| 00:00:26 |       |       |    200 |00:00:00.01 |     322 | 29696 | 29696 |26624  (0)|
|   7 |        PARTITION RANGE ITERATOR           |                       |      1 |    780K|    68M|       |   624K  (1)| 00:00:25 |     3 |     2 |    400 |00:00:00.01 |     322 |       |       |          |
|*  8 |         COUNT STOPKEY                     |                       |      2 |        |       |       |            |          |       |       |    400 |00:00:00.01 |     322 |       |       |          |
|*  9 |          TABLE ACCESS BY LOCAL INDEX ROWID| FR_MESSAGE_PART       |      2 |    780K|    68M|       |   624K  (1)| 00:00:25 |     3 |     2 |    400 |00:00:00.01 |     322 |       |       |          |
|* 10 |           INDEX RANGE SCAN DESCENDING     | IX_FR_MESSAGE_PART_TS |      2 |    559K|       |       | 44368   (1)| 00:00:02 |     3 |     2 |    400 |00:00:00.01 |       8 |       |       |          |
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Outline Data
-------------

  /*+
      BEGIN_OUTLINE_DATA
      IGNORE_OPTIM_EMBEDDED_HINTS
      OPTIMIZER_FEATURES_ENABLE('12.1.0.2')
      DB_VERSION('12.1.0.2')
      OPT_PARAM('optimizer_dynamic_sampling' 0)
      OPT_PARAM('_optimizer_dsdir_usage_control' 0)
      FIRST_ROWS(200)
      OUTLINE_LEAF(@"SEL$3")
      OUTLINE_LEAF(@"SEL$2")
      OUTLINE_LEAF(@"SEL$1")
      OUTLINE_LEAF(@"SEL$4")
      NO_ACCESS(@"SEL$4" "from$_subquery$_004"@"SEL$4")
      NO_ACCESS(@"SEL$1" "from$_subquery$_001"@"SEL$1")
      NO_ACCESS(@"SEL$2" "from$_subquery$_002"@"SEL$2")
      INDEX_RS_DESC(@"SEL$3" "FR_MESSAGE_PART"@"SEL$3" ("FR_MESSAGE_PART"."TS"))
      END_OUTLINE_DATA
  */

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

   1 - filter("from$_subquery$_004"."rowlimit_$$_rownumber">180)
   4 - filter(ROWNUM<=200)
   6 - filter(ROWNUM<=200)
   8 - filter(ROWNUM<=200)
   9 - filter("ID">=376894993815568384)
  10 - access("TS"<:2)
       filter((INTERNAL_FUNCTION("TS")+0>=:1 AND "TS"<:2))

実際にフェッチされた行と時間が、オプティマイザーの見積もりよりどれだけ優れているかに注目してください。


アップデート

この最適な計画でさえ失敗してローカル インデックスのフル スキャンが遅くなる可能性があることに注意してください。これは、クエリ フィルターに一致するのに十分なレコードが最下位のパーティションに含まれていないために、パーティション削除の下限が低すぎると推測された場合です。

rleishman のTuning "BETWEEN" Queriesは次のように述べています。

問題は、インデックスが範囲述語 (<、>、LIKE、BETWEEN) を使用して 1 つの列のみをスキャンできることです。したがって、インデックスに lower_bound 列と upper_bound 列の両方が含まれていたとしても、インデックス スキャンでは、lower_bound <= :b に一致するすべての行が返され、upper_bound >= :b に一致しない行がフィルター処理されます。

検索された値が中間のどこかにある場合、範囲スキャンは、単一の行を見つけるために、テーブル内の行の半分を返します。最も一般的に検索される行が一番上 (最大値) にあるという最悪のケースでは、インデックス スキャンは、検索ごとにテーブル内のほぼすべての行を処理します。

残念ながら、Oracle は STOPKEY COUNT 条件に達するか、パーティション全体をスキャンするまで、範囲スキャン フィルターの下限を考慮しません。

そのため、パーティション削除の下限バウンド ヒューリスティックを、タイムスタンプ期間の下限が属する同じ月に制限する必要がありました。これにより、一部の遅延トランザクション メッセージがリストに表示されないというリスクを犠牲にして、完全なインデックス スキャンを防ぐことができます。ただし、これは、必要に応じて指定された期間を延長することで簡単に解決できます。


+ 0また、同じトリックを適用して、動的パーティション排除境界バインディングで最適な計画を強制しようとしました:

select * from (select /*+ FIRST_ROWS(40) INDEX_RS_DESC(FR_MESSAGE_PART (TS)) */ * from FR_MESSAGE_PART
where TS+0 >= :1 and TS < :2
  and ID >= :3 and ID+0 < :4
order by TS DESC) where ROWNUM <= 40;

その後、プランはパーティションごとに適切な値を保持しますが、プラン テーブルの列でSTOPKEY COUNTわかるように、上限のパーティションの削除は失われます。Pstart

----------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                                      | Name                  | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
----------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                               |                       |    40 | 26200 |  9083   (1)| 00:00:01 |       |       |
|*  1 |  COUNT STOPKEY                                 |                       |       |       |            |          |       |       |
|   2 |   VIEW                                         |                       |   153 |    97K|  9083   (1)| 00:00:01 |       |       |
|*  3 |    SORT ORDER BY STOPKEY                       |                       |   153 | 14076 |  9083   (1)| 00:00:01 |       |       |
|   4 |     PARTITION RANGE ITERATOR                   |                       |   153 | 14076 |  9082   (1)| 00:00:01 |    10 |   KEY |
|*  5 |      COUNT STOPKEY                             |                       |       |       |            |          |       |       |
|*  6 |       TABLE ACCESS BY LOCAL INDEX ROWID BATCHED| FR_MESSAGE_PART       |   153 | 14076 |  9082   (1)| 00:00:01 |    10 |   KEY |
|*  7 |        INDEX RANGE SCAN DESCENDING             | IX_FR_MESSAGE_PART_TS | 11023 |       |   891   (1)| 00:00:01 |    10 |   KEY |
----------------------------------------------------------------------------------------------------------------------------------------

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

   1 - filter(ROWNUM<=40)
   3 - filter(ROWNUM<=40)
   5 - filter(ROWNUM<=40)
   6 - filter("ID">=TO_NUMBER(:3) AND "ID"+0<TO_NUMBER(:4))
   7 - access("TS"<TO_TIMESTAMP(:2))
       filter(INTERNAL_FUNCTION("TS")+0>=:1 AND "TS"<TO_TIMESTAMP(:2))
于 2018-05-10T11:14:08.847 に答える
1

動的 SQL はオプションですか? そうすれば、バインド変数を使用せずに TRX_ID および CREATE_TS フィルター値を「注入」できます。おそらく、生成された計画には COUNT STOPKEY が含まれます。

動的 SQL とは、SQL を動的に構築し、EXECUTE IMMEDIATE または OPEN で呼び出すことを意味していました。これを使用すると、バインド変数なしでフィルターを直接使用できます。例:

    v_sql VARCHAR2(1000) :=
    'select rd from (
        select /*+ INDEX(OUT_SMS OUT_SMS_CREATE_TS_TRX_ID_IX) */ rowid rd
        from OUT_SMS     
        where  TRX_ID between ' || v_trx_id_min || ' and ' || v_trx_id_maxb || '      
           and CREATE_TS between ' || v_create_ts_min|| ' and ' || v_create_ts_max || '
        order by CREATE_TS DESC, TRX_ID DESC
    ) where rownum <= 20';

次に、次を使用して呼び出します。

    EXECUTE IMMEDIATE v_sql;

あるいは:

    OPEN cursor_out FOR v_sql;
于 2013-03-18T16:43:11.230 に答える