选择整个分区使用索引-为什么?

时间:2019-05-31 10:57:09

标签: oracle oracle11g sql-execution-plan

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

测试表:

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)

2 个答案:

答案 0 :(得分:2)

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

当我运行示例代码时,空表包含72 MB,但空索引包含0.5 MB:

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的空间算法经过优化,可以在分区中存储大量数据。


不幸的是,这可能对您解决 real 问题没有帮助。您提到了一个3亿行表,这种大小应该可以更好地用于分区。

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

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

答案 1 :(得分:0)

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

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