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");

我有 sql 查询,它选择按日期和事务排序的 20 条记录:

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

Oracle 已生成下一个计划:

    -----------------------------------------------------------------------------------------------------------------------------------
    | 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必须弄清楚您正在访问哪些分区。然后它会检查以确保操作之间的第一个变量实际上确实比第二个变量(filter第二个计划中的操作)具有更低的值。

这可以很容易地重现,如以下测试用例所示:

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在解析时将分区过滤为 a,而不是第一个示例中的确切分区。

这是文字值可以提供比绑定变量更好的性能的少数情况之一。您应该调查这是否适合您。

最后,您说您希望每个分区有 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'))   

如您所见,filter当您使用出现在sort order by stopkey. 这发生在访问索引之后。这是检查变量的值是否允许返回数据(您之间的第一个变量实际上比第二个变量的值低)。使用文字时这不是必需的,因为优化器已经知道 50 小于 100(在这种情况下)。但是,它不知道 :a 在解析时是否小于 :b 。

为什么这是我不知道的。这可能是 Oracle 有意设计的——如果为变量设置的值导致零行,则进行停止键检查是没有意义的——或者只是疏忽。

于 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 |
----------------------------------------------------------------------------

在您的示例中,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 优化器仍然错误地估计对两个整个月分区进行排序会比通过现有本地索引进行两天范围扫描要快:

-----------------------------------------------------------------------------------------------------------------------
| 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;

现在该计划使用索引,但仍然涉及对两个整个分区的慢速排序:

-----------------------------------------------------------------------------------------------------------------------------------------------
| 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 提示参考和 google 的一番挣扎后,我们发现我们还必须使用INDEX_DESCINDEX_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 COUNTfilter(TO_TIMESTAMP(:2)>TO_TIMESTAMP(:1))验证上限确实大于下限的操作有关。

考虑到这一点,我试图通过转换TS between :a and :b为等价物来欺骗 oprimizer :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))

+ 0我们必须在我们的自定义 ORM 框架中实现上述特定于 Oracle 的解决方法和分区消除边界硬编码。它允许在切换到具有本地索引的分区表后保持相同的快速分页性能。

但我希望那些冒险在没有完全控制 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 的调优“BETWEEN”查询指出:

问题是索引只能扫描具有范围谓词(<、>、LIKE、BETWEEN)的一列。所以即使一个索引同时包含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 回答