【问题标题】:selecting whole partitions uses index - why?选择整个分区使用索引 - 为什么?
【发布时间】:2019-10-17 00:39:58
【问题描述】:

我正在从分区表的某些分区中选择所有数据(Oracle 11g - 在真实情况下实际更新,但对于示例,选择显示了我想理解的相同行为)。你能解释一下为什么 Oracle 决定使用索引而不是全扫描吗?据我了解,全面扫描将是更智能的访问方法。为什么 Oracle 认为按索引 rowid 批处理的索引范围扫描 + 表访问比分区的完整扫描更智能?

测试表:

CREATE TABLE t_test
    (ID                NUMBER                 NOT NULL ENABLE, 
    PARTITION_NUMBER   NUMBER                 NOT NULL ENABLE, 
    CREATION_TIMESTAMP DATE   DEFAULT SYSDATE NOT NULL ENABLE, 
    CONSTRAINT PK_t_test PRIMARY KEY (PARTITION_NUMBER, ID) USING INDEX LOCAL
    ) 
PARTITION BY LIST (PARTITION_NUMBER) 
    (
    PARTITION P1  VALUES (1) SEGMENT CREATION IMMEDIATE,
    PARTITION P2  VALUES (2) SEGMENT CREATION IMMEDIATE,
    PARTITION P3  VALUES (3) SEGMENT CREATION IMMEDIATE,
    PARTITION P4  VALUES (4) SEGMENT CREATION IMMEDIATE,
    PARTITION P5  VALUES (5) SEGMENT CREATION IMMEDIATE,
    PARTITION P6  VALUES (6) SEGMENT CREATION IMMEDIATE,
    PARTITION P7  VALUES (7) SEGMENT CREATION IMMEDIATE,
    PARTITION P8  VALUES (8) SEGMENT CREATION IMMEDIATE,
    PARTITION P9  VALUES (9) SEGMENT CREATION IMMEDIATE
    );

从某些分区中选择数据(此示例中没有插入任何内容,但与分区中的数据行为相同):

explain plan for select * from t_test where PARTITION_NUMBER in (2,3,4,5,6,7);
SELECT * FROM TABLE(dbms_xplan.display);

Plan hash value: 3284178661

-------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                                   | Name      | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
-------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                            |           |     1 |    35 |     1   (0)| 00:00:01 |       |       |
|   1 |  INLIST ITERATOR                            |           |       |       |            |          |       |       |
|   2 |   PARTITION LIST ITERATOR                   |           |     1 |    35 |     1   (0)| 00:00:01 |KEY(I) |KEY(I) |
|   3 |    TABLE ACCESS BY LOCAL INDEX ROWID BATCHED| T_TEST    |     1 |    35 |     1   (0)| 00:00:01 |KEY(I) |KEY(I) |
|*  4 |     INDEX RANGE SCAN                        | PK_T_TEST |     1 |       |     2   (0)| 00:00:01 |KEY(I) |KEY(I) |
-------------------------------------------------------------------------------------------------------------------------

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

   4 - access("PARTITION_NUMBER"=2 OR "PARTITION_NUMBER"=3 OR "PARTITION_NUMBER"=4 OR "PARTITION_NUMBER"=5 OR 
              "PARTITION_NUMBER"=6 OR "PARTITION_NUMBER"=7)

Note
-----
   - dynamic statistics used: dynamic sampling (level=2)

我不明白为什么它会进行范围扫描...为什么不只是这样(相同的查询但带有完整提示)?为什么它认为在这里使用索引是更好的方法? (即使是 ALL_ROWS 提示也不会改变行为):

explain plan for select /*+ full(t_test) */ * from t_test where PARTITION_NUMBER in (2,3,4,5,6,7);
SELECT * FROM TABLE(dbms_xplan.display);

Plan hash value: 3335595461

------------------------------------------------------------------------------------------------
| Id  | Operation             | Name   | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |        |     1 |    35 |     2   (0)| 00:00:01 |       |       |
|   1 |  PARTITION LIST INLIST|        |     1 |    35 |     2   (0)| 00:00:01 |KEY(I) |KEY(I) |
|   2 |   TABLE ACCESS FULL   | T_TEST |     1 |    35 |     2   (0)| 00:00:01 |KEY(I) |KEY(I) |
------------------------------------------------------------------------------------------------

Note
-----
   - dynamic statistics used: dynamic sampling (level=2)

【问题讨论】:

    标签: oracle oracle11g sql-execution-plan


    【解决方案1】:

    由于段空间的分配方式,该示例使用索引而不是全表扫描。默认情况下,Oracle 倾向于为表分区分配大量空间。对于索引,Oracle 倾向于为索引分区分配更少的空间。

    当我运行您的示例代码时,空表包含 72 兆字节,但空索引包含 0.5 兆字节:

    select segment_name, sum(bytes)/1024/1024 mb
    from user_segments
    where segment_name in ('T_TEST', 'PK_T_TEST')
    group by segment_name
    order by 1 desc;
    
    SEGMENT_NAME   MB
    ------------   -------
    T_TEST         72
    PK_T_TEST       0.5625
    

    全表扫描必须读取整个段。索引范围扫描仍然需要从表中读取,但它可以使用 ROWID 来执行此操作,因此从磁盘读取的数据更少。

    Oracle 的自动段空间分配几乎总是比手动配置好。但是对于少量的数据,优化细分可能是有意义的。如果将 9 个SEGMENT CREATION IMMEDIATE 中的每一个都更改为STORAGE (INITIAL 64K NEXT 64K),那么表分区会更小,执行计划将使用全表扫描。但你可能不想这样做。此问题可能仅与使用不切实际的小样本数据集有关。

    可以理解,Oracle 的空间算法针对在分区中存储大量数据进行了优化。


    不幸的是,这可能无法帮助您解决真正的问题。您提到了一个 3 亿行的表,这种大小应该更适合分区。

    但真正的问题也可能与段空间问题有关。也许这张表曾经有 30 亿行,被删除了,但从未缩小?或者,该表可能是由数百个分区创建的,其中大部分是空的。或者,该表可能是使用一些荒谬的手动空间设置创建的。

    在处理大型表性能时,我们通常必须考虑段和字节而不是行数。

    【讨论】:

    • 哇,谢谢,这很有道理!我将在星期一以字节而不是行的形式查看真实数据,并让您知道!再次感谢!
    • 可以确认。分区中的空白空间导致了这种情况。 TYVM。我在这里学到了很多东西。
    【解决方案2】:

    因为索引告诉oracle您的数据在哪个分区。当您说“全扫描”时,您是指全扫描表,还是读取指定分区中的所有行?第二个解释计划中的“TABLE ACCESS FULL”行在PstartPstop 列中有KEY(I),因此Oracle 只会扫描这些分区中的所有行,而不是整个表。

    因此,分区限制了仅读取 where 子句中的分区,它读取特定分区中的所有行并使用索引来确定要读取的分区。它使用范围扫描来查找主键中的前导列值。在任一解释计划中,它都使用索引来查看包含指定PARTITION_NUMBER 的分区——在第一个解释计划中,您看到4 = access 意味着它正在使用索引。

    【讨论】:

    • TYVM!对不起,但我不听你的回答。我知道在这两个计划中它只读取 where 子句中的分区。但是在第一个计划中——如果我不通过提示强制它使用第二个计划,Oracle 使用的那个——它还读取索引。我不明白为什么当 Oracle 知道我想要这些分区中的所有行时,它更喜欢读取两件事(索引和表)而不是一件事(表)。我不明白 Oracle 认为使用索引有什么好处。我也不认为当你说“在任何一个解释计划中,它都在使用索引......”这是正确的。
    • 想象一下(就像我想解决的实际问题一样)每个分区中有 3 亿条记录。我发现 Oracle 通过索引中的 rowid 访问每条记录是荒谬的。荒谬,即使在“批处理”(按本地索引 ROWID 批处理的表访问)模式下。不是吗?还是我在这里误解了什么?
    • 那么您是否有一个从实际数据库输出的解释计划输出,每个分区有 300 条 MM 记录?
    • 按照上面@MarkStewart 所说的,您能否尝试使用 300MM 行并确保您有统计数据。然后向我们展示执行计划
    猜你喜欢
    • 1970-01-01
    • 2021-02-02
    • 2011-06-08
    • 1970-01-01
    • 1970-01-01
    • 2016-11-30
    • 2021-04-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多