快速分页结果,适用于各种过滤器子句

Mat*_* M. 3 oracle oracle-11g execution-plan

我一直致力于从我的数据库 (11g) 中的表中获取分页结果。虽然我有一个有效的查询(即结果是正确的),但它的性能不如我希望的那样好,我正在努力提高它的效率(估计仅在该查询上每秒调用 60 次)。

所以首先,我阅读了在 Oracle 中计算页数的有效方法是什么?,并且文章也指出了这一点,但不幸的是,它根本没有讨论所提出的查询的执行计划(我会在星期一看到它们)。

这是我提出的查询(表分区part_code是日期范围):

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

注意:大多数查询将使用单个func_xxx过滤器进行,我认为 M 会很小......但不能保证,理论上 M 可以达到 9999。

注意:〜72个分区中总,但只有〜39活性至多,〜300000个的不同的值func_col1(但有一些具有〜50000行和一些具有1行),〜10倍的值typeid是唯一的(通过一个序列生成)。

它确实有效,但执行计划中有一个令人讨厌的惊喜:第二个查询(with rownum as rnum)已完全执行,在分页开始之前从数据库中提取了大约 50,000 行。从数据库中提取了大约 50,000 行以返回其中约 30 个似乎特别低效。

在执行计划中,这显示为:

View
\_ Filter (rnum)
   \_ View <= here comes the trouble
      \_ Sort
         \_ ...
Run Code Online (Sandbox Code Playgroud)

如有必要,我可以在表上创建索引,并且可以转换现有的分区索引 ( part_code, func_col1, date, type, id)。索引完全符合我的order by条款所要求的顺序;但这似乎还不够(并且删除 order by 子句并没有让我更接近)。

有没有办法防止视图的“具体化”,并让 Oracle 在外部查询需要更多数据时动态构建它(即,转向内部查询的延迟评估)?

Mat*_*Mat 5

我相信您应该制定一个避免任何实际排序操作的计划,并尽快“停止”。

为了避免排序(并“具体化”内部视图),您的排序顺序必须与索引列完全匹配,或者您的 where 子句必须仅在所有前导列上严格等于。否则将需要对子集进行排序,这将需要评估整个内部视图。

这是一个带有假设foo表的示例:

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

与您的选择最接近的简单等效项:

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

解释计划不好:

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

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

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

计数为时已晚,并且在这种情况下实际上(我认为)并不是“停止工作”。

将索引列添加到订单中(可能不符合您的要求,抱歉):

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

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

   1 - filter("R"<=10 AND "R">=5)
   5 - access(("A"=3 OR "A"=4) AND "B"=3)
Run Code Online (Sandbox Code Playgroud)

至少我们摆脱了那种。现在让我们试着“停下来”:

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

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

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

这对于您的查询来说可能就足够了:注意count stopkey(rownum <魔术,不会between在我所看到的内容中起作用) 和“行”列 - 内部扫描不必费心在找到后获取更多行N

有了上面的内容,您仍然会N从表中读取行。

如果所有搜索条件都被索引,您可以限制:执行上述过滤但仅从ROWID每个匹配项中检索而不是所有列,然后通过ROWID.

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

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

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

如果任何搜索字段不在索引中,这将不起作用。并确保您使用真实数据和通常的搜索条件对其进行跟踪,以查看它是否有实质性影响 - 实际上可能更糟,尤其是对于低值M(这可能是您想要最快的情况)或高值N-M.