分区索引(Partition Index)与SQL执行计划

转载:http://space.itpub.net/17203031/viewspace-713129


分区技术Partition)是Oracle从8版本开始推出的一个新技术。经历若干年的发展,分区技术不断成熟、在Oracle各产品线中处的地位愈发重要。从技术方案布局上看,分区技术还是属于Oracle VLDB(Very Large Database)解决方案。

 

1、从分区Partition技术到分区索引

 

就分区技术的目的,Oracle是要实现三个优势目标,归纳起来就是管理和性能:

 

ü       优化管理:分区技术主要解决的还是管理问题。一个很大的数据对象,如果数据活跃程度或者处理存在活跃差异,就可以将其分割在不同的分区。这样,在进行分区管理,如删除、新增加、移动等操作的时候,就不会影响到其他分区;

ü       数据库可用性提升:一般海量数据表,存在更大的坏块风险和I/O均衡压力,我们可以将分区放置在不同的位置上。这样,分区之间相互不影响,如果一个分区有故障,其他分区的访问仍能正常进行;

ü       性能提升:这里说的性能提升指的是分区裁剪(Partition Pruning)。在业务合理的情况下,当SQL条件where中出现适当的分区条件,那么执行过程只会对特定的分区进行操作,这样可以大大提高执行性能;

 

对分区技术,我们要重点关注两个问题:

 

ü       数据是不是海量,完全不是我们规划分区的根本理由。确定分区技术的关键在于数据对象在业务操作上是否有分区特性。应用分区技术之后,可否提升分区效率;

ü       分区表不是唯一可分区的对象。索引Index也是具有分区属性的,而且索引的分区与对应数据表是否分区无关;

 

本篇,我们一起对分区索引(Partition Index)进行简单的分析研究,探讨应用的场景和现象。

 

2、分区索引的类型

 

分区索引从分类上,有两个标准:局部(Local)和全局(Global)、前导(Prefix)和非前导(Non-Prefix)。

 

局部(Local)和全局(Global)是针对与对应的分区键而言的,如果一个分区索引符合下面的条件,就可以称为Local索引:

 

ü       与分区数据表具有相同数据的分区/子分区;

ü       与分区数据表具有相同的分区限制,也就是分区条件相同;

ü       与分区数据表具有相同的分区键;

 

如果一个分区索引不是Local的,那么就是Global的。

 

前导Prefix和非前导Non-Prefix是针对分区表的主键是否出现在索引index的左侧前导列中。如果出现,我们称之为Prefix Index,否则就是Non-Prefix Index

 

针对不同的数据表(分区和非分区)和不同的数据访问方式,使用适当类型的索引,可以让CBO优化器获取到最好的执行效率。

 

下面,我们针对不同的数据表和索引类型,分析执行计划情况。

 

我们先根据分区表的情况进行分析。

 

3、分区表下的各类型索引情况

 

我们在10g下进行试验,针对的是分区表。

 

 

SQL> select * from v$version;

BANNER

----------------------------------------------------------------

Oracle Database10gEnterpriseEdition Release10.2.0.1.0 - Prod

PL/SQL Release10.2.0.1.0 - Production

CORE    10.2.0.1.0      Production

TNS for 32-bit Windows: Version10.2.0.1.0 - Production

NLSRTL Version10.2.0.1.0 - Production

 

SQL> create table t

 2 partition by list(owner)

 3 (

 4    partition t_list_sys values ('SYS'),

 5    partition t_list_scott values ('SCOTT'),

 6    partition t_list_hr values ('HR'),

 7    partition t_list_others values (default)

 8 )

 9 as select * from dba_objects where 1=0;

Table created

 

--选取分区键owner,划分分区;

SQL> insert into t select * from dba_objects;

106610 rows inserted

 

SQL> commit;

Commit complete

 

 

此时,根据分区表特性,Oracle会创建出多个segment对象与数据表对应。

 

 

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

PL/SQL procedure successfully completed

 

SQL> select segment_name, partition_name, segment_type, bytes from dba_segments where wner='SCOTT' and segment_name='T';

 

SEGMENT_NAME        PARTITION_NAME       SEGMENT_TYPE  BYTES

-------------------- ------------------------------ ------------------ ----------

T             T_LIST_SYS                    TABLE PARTITION      6291456

T             T_LIST_SCOTT                  TABLE PARTITION        65536

T             T_LIST_HR                     TABLE PARTITION        65536

T            T_LIST_OTHERS                 TABLE PARTITION      7340032

 

 

下面是索引,首先我们创建普通的全局索引,就是一个数据表对应一个索引类型。

 

 

SQL> create index idx_t_id on t(object_id);

Index created

 

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

PL/SQL procedure successfully completed

 

SQL> select segment_name, partition_name,segment_type, bytes from dba_segments where wner='SCOTT' and segment_name='IDX_T_ID';

SEGMENT_NAME        PARTITION_NAME        SEGMENT_TYPE BYTES

-------------------- ------------------------------ ------------------ ----------

IDX_T_ID                                          INDEX           3145728

 

 

下面分别寻找三个查询业务场景,对SQL语句执行计划进行检索。

 

 

SQL> explain plan for select * from t where object_id=1000;

Explained

 

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT

--------------------------------------------------------------------------------

Plan hash value: 2226237847

--------------------------------------------------------------------------------

| Id | Operation                         | Name    | Rows | Bytes | Cost (%C

--------------------------------------------------------------------------------

|  0 | SELECT STATEMENT                  |         |    2 |  188 |    3

|  1 | TABLE ACCESS BY GLOBAL INDEX ROWID| T       |    2 |  188 |    3

|* 2 |  INDEX RANGE SCAN                | IDX_T_ID |    2 |      |    1

--------------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

  2 - access("OBJECT_ID"=1000)

14 rows selected

 

 

注意,这个执行计划值得关注的是数据表索引访问方式。条件object_id=1000,正好落在全局索引的叶子节点上,可以直接定位到条件的rowid。在根据rowid定位数据行的过程中,检索了数据表分区。这个操作就是根据全局索引返回rowid定位数据行过程,称作“TABLE ACCESS BY GLOBAL INDEX ROWID”。

 

 

 

SQL> explain plan for select * from t where object_id=1000 and wner='SCOTT';

Explained

 

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT

--------------------------------------------------------------------------------

Plan hash value: 882533222

--------------------------------------------------------------------------------

| Id | Operation            | Name | Rows | Bytes | Cost (%CPU)| Time    | P

--------------------------------------------------------------------------------

|  0 | SELECT STATEMENT     |     |    1 |   85 |    3  (0)| 00:00:01 |

|  1 | PARTITION LIST SINGLE|     |    1 |   85 |    3  (0)| 00:00:01 |

|* 2 |  TABLE ACCESS FULL  | T   |    1 |   85 |    3  (0)| 00:00:01 |

--------------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

  2 - filter("OBJECT_ID"=1000)

14 rows selected

 

 

SQL> explain plan for select * from t where object_id=1000 and wner='SYS';

Explained

 

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT

--------------------------------------------------------------------------------

Plan hash value: 3317687338

--------------------------------------------------------------------------------

| Id | Operation                         | Name    | Rows | Bytes | Cost (%C

--------------------------------------------------------------------------------

|  0 | SELECT STATEMENT                  |         |    2 |  186 |    6

|* 1 | TABLE ACCESS BY GLOBAL INDEX ROWID| T       |    2 |  186 |    6

|* 2 |  INDEX RANGE SCAN                | IDX_T_ID |    5 |      |    1

--------------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

  1 - filter("OWNER"='SYS')

  2 - access("OBJECT_ID"=1000)

 

15 rows selected

 

 

上面两个SQL体现出CBO的运算规则和工作方式。

 

当查询条件为object_id=1000 and wner=’SCOTT’的时候,Oracle选择了先在一个分区上定位(owner=’SCOTT’),也就是“PARTITION LIST SINGLE”。定位之后,Oracle有两个选择,一个是搜索Global Index,依据条件object=1000。另一种是进行分区内全表扫描。

 

全局索引只是一个段segment对象,体积包括了所有的object_id值。进行检索消耗的成本要大于只对scott分区全表扫描的成本。所以此处,Oracle CBO选择了全表扫描子分区。

 

当查询条件为owner=’SYS’之后,事情有所不同。SYS分区大小超过全局索引大小。如果选择落入分区空间,之后全表扫描或者索引扫描,成本都是不容易接受的。所以,Oracle放弃了SYS条件,先搜索索引树,按照条件object_id=1000检索,最后根据全局索引返回的rowid,直接定位到结果行。

 

这两个SQL,条件相同,不同在于条件的取值不同,统计量引起计算成本有差别。最终造成选择出的执行计划有差异。

 

下面,我们继续讨论当有分区索引的时候,执行计划是如何进行选取?


分区索引(Partition Index)与SQL执行计划(中)

上一篇 / 下一篇  2011-12-08 21:34:38 / 个人分类:Oracle性能优化

上篇(http://space.itpub.net/17203031/viewspace-712904)中,我们着重讨论了分区技术和全局索引在分区表中的一些现象。注意,我们讨论的是无前导索引,那么如果是有前导prefix索引,现象是如何的呢?

 

 

SQL> drop index idx_t_id;

Index dropped

 

--分区键单独作为前导列;

SQL> create index idx_t_owner on t(owner);

Index created

 

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

PL/SQL procedure successfully completed

 

SQL> select segment_name, partition_name,segment_type, bytes from dba_segments where wner='SCOTT' and segment_name='IDX_T_OWNER';

 

SEGMENT_NAME  PARTITION_NAME           SEGMENT_TYPE   BYTES

-------------------- ------------------------------ ------------------ ----------

IDX_T_OWNER                                INDEX                3145728

 

 

此时,我们观察一下三个应用场景的执行计划

 

 

SQL> explain plan for select * from t where object_id=1000;

Explained

 

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT

--------------------------------------------------------------------------------

Plan hash value: 3845649146

--------------------------------------------------------------------------------

| Id | Operation         | Name | Rows | Bytes | Cost (%CPU)| Time    | Psta

--------------------------------------------------------------------------------

|  0 | SELECT STATEMENT  |     |    2 |  188 |  351  (2)| 00:00:05 |

|  1 | PARTITION LIST ALL|     |    2 |  188 |  351  (2)| 00:00:05 |

|* 2 |  TABLE ACCESS FULL| T   |    2 |  188 |  351  (2)| 00:00:05 |

--------------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

  2 - filter("OBJECT_ID"=1000)

 

14 rows selected

 

 

上面的一种SQL场景,是我们设计Oracle分区表和分区索引的一种大忌!如果上面的SQL是该数据表的一种主要访问方式,我们可以认为该设计是存在瑕疵和潜在性能问题的。

 

如果一个分区表,没有进行分区裁剪的进行全表扫描,要跨过多个分区进行操作。这样的总成本消耗量要远远大于不进行分区操作。所以,如果对一个分区表进行操作,要优先将分区键作为检索条件的一部分,利用上分区裁剪特性!

 

在上面的SQL中,object_id=1000既不是分区键,也不是索引列。所以,只能对所有分区进行检索(PARTITION LIST ALL),分区内部进行全表扫描。

 

 

SQL> explain plan for select * from t where object_id=1000 and wner='SCOTT';

Explained

 

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT

--------------------------------------------------------------------------------

Plan hash value: 882533222

--------------------------------------------------------------------------------

| Id | Operation            | Name | Rows | Bytes | Cost (%CPU)| Time    | P

--------------------------------------------------------------------------------

|  0 | SELECT STATEMENT     |     |    1 |   85 |    3  (0)| 00:00:01 |

|  1 | PARTITION LIST SINGLE|     |    1 |   85 |    3  (0)| 00:00:01 |

|* 2 |  TABLE ACCESS FULL  | T   |    1 |   85 |    3  (0)| 00:00:01 |

--------------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

  2 - filter("OBJECT_ID"=1000)

14 rows selected

 

SQL> explain plan for select * from t where object_id=1000 and wner='SYS';

Explained

 

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT

--------------------------------------------------------------------------------

Plan hash value: 882533222

--------------------------------------------------------------------------------

| Id | Operation            | Name | Rows | Bytes | Cost (%CPU)| Time    | P

--------------------------------------------------------------------------------

|  0 | SELECT STATEMENT     |     |    2 |  186 |  154  (2)| 00:00:02 |

|  1 | PARTITION LIST SINGLE|     |    2 |  186 |  154  (2)| 00:00:02 |

|* 2 |  TABLE ACCESS FULL  | T   |    2 |  186 |  154  (2)| 00:00:02 |

--------------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

  2 - filter("OBJECT_ID"=1000)

 

14 rows selected

 

 

当全局Global索引,以分区键作为索引的时候,如果检索条件中出现分区键等于条件,那么直接分区定位检索就会出现在成本很低的执行计划中。就如同上面的两个SQL执行计划,优先进行分区定位,之后在进行分区内部的检索。

 

从上面的三个场景,我们可以得到一部分的结论:如果分区表,同时使用分区键作为Global索引的索引列时,通常索引是在执行计划中不会发生很重要的作用的。而且当SQL条件中没有出现分区键,执行计划是存在很大的性能风险的。

 

 

那么,我们换一下带prefix的组合索引,观察一下执行情况如何?

 

 

SQL> drop index idx_t_owner;

Index dropped

 

--以分区键作为前导列,结合关键搜索条件;

SQL> create index idx_t_ownerid on t(owner,object_id);

Index created

 

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

PL/SQL procedure successfully completed

 

 

SQL> select segment_name, partition_name,segment_type, bytes from dba_segments where wner='SCOTT' and segment_name='IDX_T_OWNERID';

 

SEGMENT_NAME        PARTITION_NAME         SEGMENT_TYPE   BYTES

-------------------- ------------------------------ ------------------ ----------

IDX_T_OWNERID                                   INDEX           4194304

 

 

此时,我们看一下三个关键SQL的执行情况。

 

 

SQL> explain plan for select * from t where object_id=1000;

Explained

 

SQL> select * from table(dbms_xplan.display);

 

PLAN_TABLE_OUTPUT

--------------------------------------------------------------------------------

Plan hash value: 3525553998

--------------------------------------------------------------------------------

| Id | Operation                         | Name         | Rows | Bytes | Cos

--------------------------------------------------------------------------------

|  0 | SELECT STATEMENT                  |              |    2 |  188 |

|  1 | TABLE ACCESS BY GLOBAL INDEX ROWID| T            |    2 |  188 |

|* 2 |  INDEX SKIP SCAN                 | IDX_T_OWNERID |    2 |      |

--------------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

  2 - access("OBJECT_ID"=1000)

      filter("OBJECT_ID"=1000)

 

15 rows selected

 

 

当分区键不出现,同时存在object_id包含的索引时,索引路径是执行计划中必然出现的一种。注意:由于object_id在索引中位于非前导列的位置,所以此处Oracle的执行计划是进行index skip scan跳跃式检索

 

下面两个是带有分区键的数据SQL

 

 

SQL> explain plan for select * from t where object_id=1000 and wner='SCOTT';

Explained

 

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT

--------------------------------------------------------------------------------

Plan hash value: 882533222

--------------------------------------------------------------------------------

| Id | Operation            | Name | Rows | Bytes | Cost (%CPU)| Time    | P

--------------------------------------------------------------------------------

|  0 | SELECT STATEMENT     |     |    1 |   85 |    3  (0)| 00:00:01 |

|  1 | PARTITION LIST SINGLE|     |    1 |   85 |    3  (0)| 00:00:01 |

|* 2 |  TABLE ACCESS FULL  | T   |    1 |   85 |    3  (0)| 00:00:01 |

--------------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

  2 - filter("OBJECT_ID"=1000)

14 rows selected

 

SQL> explain plan for select * from t where object_id=1000 and wner='SYS';

Explained

 

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT

--------------------------------------------------------------------------------

Plan hash value: 3490045046

--------------------------------------------------------------------------------

| Id | Operation                         | Name         | Rows | Bytes | Cos

--------------------------------------------------------------------------------

|  0 | SELECT STATEMENT                  |              |    2 |  186 |

|  1 | TABLE ACCESS BY GLOBAL INDEX ROWID| T            |    2 |  186 |

|* 2 |  INDEX RANGE SCAN                | IDX_T_OWNERID |    5 |      |

--------------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

  2 - access("OWNER"='SYS' AND "OBJECT_ID"=1000)

 

14 rows selected

 

 

两句带分区键的“同型异值”SQL又出现了执行计划不相同的情况。我们分别进行说明。

 

在第一个SQLowner=’SCOTT’)中,由于scott分区很小,一次性的分区裁剪可以减少很多的麻烦。同时,即使使用索引,Global对应的索引结构体积甚至可能会超过scott分区的大小,这样还不如进行FTS全表扫描操作的成本更可以接受。

 

第二个SQL中,有所不同,因为SYS分区体积很大,超过了索引结构。这样,Oracle放弃了分区裁剪的路径,直接选择直接的索引树定位结果rowid的方法。

 

也就是说,当全局索引中,采用prefix方式的组合索引列时,分区裁剪和索引路径难以共存。

 

总结论是:在分区表情况下,Global索引有无prefix意义不大。

 

 

下面我们来看看分区索引的情况。分区索引默认的方式我们选择Local,也就是与数据表分区对应的方式。

 

 

SQL> drop index IDX_T_OWNERID;

Index dropped

 

--加入local关键字之后,表明建立Local分区索引;

SQL> create index IDX_T_ID on t(object_id)local;

Index created

 

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

PL/SQL procedure successfully completed

 

 

SQL> select segment_name, partition_name,segment_type, bytes from dba_segments where wner='SCOTT' and segment_name='IDX_T_ID';

 

SEGMENT_NAME  PARTITION_NAME          SEGMENT_TYPE     BYTES

-------------------- ------------------------------ ------------------ ----------

IDX_T_ID            T_LIST_SYS           INDEX PARTITION       917504

IDX_T_ID            T_LIST_SCOTT        INDEX PARTITION        65536

IDX_T_ID            T_LIST_HR            INDEX PARTITION        65536

IDX_T_ID            T_LIST_OTHERS       INDEX PARTITION      2097152

 

 

注意:所谓的分区索引,就是索引段(Index Segment)也被分割为多个段对象。如果我们不指定索引分区键,那么采用的分区方式同分区表的方式是相同的。

 

那么,我们一起看看分区索引的效果。

 

 

SQL> explain plan for select * from t where object_id=1000;

Explained

 

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT

--------------------------------------------------------------------------------

Plan hash value: 1743619291

--------------------------------------------------------------------------------

| Id | Operation                         | Name    | Rows | Bytes | Cost (%C

--------------------------------------------------------------------------------

|  0 | SELECT STATEMENT                  |         |    2 |  188 |    7

|  1 | PARTITION LIST ALL               |         |    2 |  188 |    7

|  2 |  TABLE ACCESS BY LOCAL INDEX ROWID|T       |    2 |  188 |    7

|* 3 |   INDEX RANGE SCAN               | IDX_T_ID |    2 |      |    5

--------------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

  3 - access("OBJECT_ID"=1000)

15 rows selected

 

 

当使用object_id=1000时,执行计划要注意两个细节。首先,由于分区键owner没有出现的SQL语句中,无论是分区数据表还是分区索引,都无法进行分区裁剪操作的。

 

此处,Oracle CBO选择出的最优执行计划:首先是检索每一个索引分区,也就是其中的PARTITION LIST ALL,在每个分区中检索object_id=1000的条件,进行Index Range Scan。将查询出符合条件的rowid返回,进行回表操作Table Access By Local Index Rowid

 

 

SQL> explain plan for select * from t where object_id=1000 and wner='SCOTT';

Explained

 

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT

--------------------------------------------------------------------------------

Plan hash value: 4224690800

--------------------------------------------------------------------------------

| Id | Operation                         | Name    | Rows | Bytes | Cost (%C

--------------------------------------------------------------------------------

|  0 | SELECT STATEMENT                  |         |    1 |   85 |    2

|  1 | PARTITION LIST SINGLE            |         |    1 |   85 |    2

|* 2 |  TABLE ACCESS BY LOCAL INDEX ROWID| T       |    1 |   85 |    2

|* 3 |   INDEX RANGE SCAN               | IDX_T_ID |    1 |      |    1

--------------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

  2 - filter("OWNER"='SCOTT')

  3 - access("OBJECT_ID"=1000)

 

16 rows selected

 

SQL> explain plan for select * from t where object_id=1000 and wner='SYS';

Explained

 

SQL> select * from table(dbms_xplan.display);

 

PLAN_TABLE_OUTPUT

--------------------------------------------------------------------------------

Plan hash value: 4224690800

--------------------------------------------------------------------------------

| Id | Operation                         | Name    | Rows | Bytes | Cost (%C

--------------------------------------------------------------------------------

|  0 | SELECT STATEMENT                  |         |    2 |  186 |    3

|  1 | PARTITION LIST SINGLE            |         |    2 |  186 |    3

|* 2 |  TABLE ACCESS BY LOCAL INDEX ROWID| T       |    2 |  186 |    3

|* 3 |   INDEX RANGE SCAN               | IDX_T_ID |    2 |      |    1

--------------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

  2 - filter("OWNER"='SYS')

  3 - access("OBJECT_ID"=1000)

 

16 rows selected

 

 

注意,虽然scott分区同sys分区数据差异值很大,但是执行计划是相同的。这个是我们所追求的现象。因为,如果该SQL是使用绑定变量,对应最有执行计划不同,带来的Bind Peeking风险是不能忽视的。

 

执行计划中,显然进行了分区裁剪动作。这里的两句SQL中,均出现了分区键owner条件,这里面的分区裁剪,是索引分区的选取裁剪过程,而不是数据表分区裁剪过程。

 

在每一个分区索引中,对应使用object_id=1000查询条件,从局部索引的角度进行回表操作。

 

下篇中,我们将研究prefixLocal Index的作用和效果。



分区索引(Partition Index)与SQL执行计划(下)

上一篇 / 下一篇  2011-12-11 23:24:05 / 个人分类:Oracle性能优化

 

 

上面,我们讨论了Local Index的分区特性。如果我们将分区键作为前缀加入到索引中,是什么现象呢?

 

 

SQL> drop index IDX_T_ID;

Index dropped

 

SQL> create index idx_t_owner on t(owner) local;

Index created

 

 

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

PL/SQL procedure successfully completed

 

 

SQL> select segment_name, partition_name,segment_type, bytes from dba_segments where wner='SCOTT' and segment_name='IDX_T_OWNER';

 

SEGMENT_NAME        PARTITION_NAME      SEGMENT_TYPE BYTES

-------------------- ------------------------------ ------------------ ----------

IDX_T_OWNER         T_LIST_SYS             INDEX PARTITION 917504

IDX_T_OWNER         T_LIST_SCOTT         INDEX PARTITION  65536

IDX_T_OWNER         T_LIST_HR            INDEX PARTITION        65536

IDX_T_OWNER         T_LIST_OTHERS       INDEX PARTITION      2097152

 

 

 

我们创建了索引idx_t_owner,选择分区键owner作为索引列。当选择Local类型时,也就意味着每个索引分区只包括相同的owner值。对应执行计划,效果如何呢?

 

 

SQL> explain plan for select * from t where object_id=1000;

Explained

 

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT

--------------------------------------------------------------------------------

Plan hash value: 3845649146

--------------------------------------------------------------------------------

| Id | Operation         | Name | Rows | Bytes | Cost (%CPU)| Time    | Psta

--------------------------------------------------------------------------------

|  0 | SELECT STATEMENT  |     |    2 |  188 |  351  (2)| 00:00:05 |

|  1 | PARTITION LIST ALL|     |    2 |  188 |  351  (2)| 00:00:05 |

|* 2 |  TABLE ACCESS FULL| T   |    2 |  188 |  351  (2)| 00:00:05 |

--------------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

  2 - filter("OBJECT_ID"=1000)

14 rows selected

 

 

对于只包括object_id=1000的查询条件。无疑,Oracle会检索所有数据表分区,并且在每个分区中进行全表扫描。

 

 

SQL> explain plan for select * from t where object_id=1000 and wner='SCOTT';

Explained

 

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT

--------------------------------------------------------------------------------

Plan hash value: 1903319282

--------------------------------------------------------------------------------

| Id | Operation                         | Name       | Rows | Bytes | Cost

--------------------------------------------------------------------------------

|  0 | SELECT STATEMENT                  |            |    1 |   85 |    2

|  1 | PARTITION LIST SINGLE            |            |    1 |   85 |    2

|* 2 |  TABLE ACCESS BY LOCAL INDEX ROWID| T          |    1 |   85 |    2

|* 3 |   INDEX RANGE SCAN               | IDX_T_OWNER |   50 |      |    1

--------------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

  2 - filter("OBJECT_ID"=1000)

  3 - access("OWNER"='SCOTT')

16 rows selected

 

SQL> explain plan for select * from t where object_id=1000 and wner='SYS';

Explained

 

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT

--------------------------------------------------------------------------------

Plan hash value: 882533222

--------------------------------------------------------------------------------

| Id | Operation            | Name | Rows | Bytes | Cost (%CPU)| Time    | P

--------------------------------------------------------------------------------

|  0 | SELECT STATEMENT     |     |    2 |  186 |  154  (2)| 00:00:02 |

|  1 | PARTITION LIST SINGLE|     |    2 |  186 |  154  (2)| 00:00:02 |

|* 2 |  TABLE ACCESS FULL  | T   |    2 |  186 |  154  (2)| 00:00:02 |

--------------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

  2 - filter("OBJECT_ID"=1000)

 

14 rows selected

 

两个同型SQL的执行计划完全不同。当owner=’SCOTT’时,Oracle会去检索索引分区,定位到owner=’SCOTT’的索引分区上,之后回表使用object_id=1000的条件检索。

 

但是,对于owner=’SYS’的语句,情况有所不同。由于该分区较大,所以Oracle选择直接去进行数据表分区的选取,最后在进行分区内部的全表扫描。

 

说明:我们说,在分区索引情况下,采用数据表分区键作为索引分区键,同时将分区键作为索引是没有什么意义的。

 

 

上面一直在讨论默认分区条件。我们在创建分区索引的时候,是可以选择非分区键作为索引分区键。

 

 

SQL> drop index IDX_T_OWNER;

Index dropped

 

SQL> create index idx_t_id on t(object_id)

 2 globalpartitionby range(object_id)

 3 (

 4    partition t_list_01 values less than (30000),

 5    partition t_list_02 values less than (70000),

 6    partition t_list_other values less than (maxvalue)

 7 );

 

Index created

 

SQL> exec dbms_stats.gather_table_stats(user,'T',cascade => true)

PL/SQL procedure successfully completed

 

 

上面代码中注意两个方面。一个是索引的global关键字。在本系列的开篇,我们就已经分清了localglobal的区别和标准。这里,我们建立了三个索引分区,与数据表的四个分区首先就是不同的,所以这里我们只能使用global关键字。

 

另一个就是我们可以自己选择索引分区的分区键以及分区类型。这里,我们就选择了object_id作为范围分区的分区键。

 

建立之后,可以清晰的看到分区段空间。

 

 

SQL> select segment_name, partition_name,segment_type, bytes from dba_segments where wner='SCOTT' and segment_name='IDX_T_ID';

 

SEGMENT_NAME        PARTITION_NAME        SEGMENT_TYPE    BYTES

-------------------- ------------------------------ ------------------ ----------

IDX_T_ID            T_LIST_01           INDEX PARTITION      2097152

IDX_T_ID            T_LIST_02           INDEX PARTITION      1048576

IDX_T_ID            T_LIST_OTHER       INDEX PARTITION       196608

 

 

此时,我们的执行计划就按照如下的规则进行。

 

 

SQL> explain plan for select * from t where object_id=1000;

 

Explained

 

SQL> select * from table(dbms_xplan.display);

 

PLAN_TABLE_OUTPUT

--------------------------------------------------------------------------------

Plan hash value: 3010955244

--------------------------------------------------------------------------------

| Id | Operation                          | Name    | Rows | Bytes | Cost (%

--------------------------------------------------------------------------------

|  0 | SELECT STATEMENT                   |         |    2 |  188 |    3

|  1 | PARTITIONRANGESINGLE            |         |    2 |  188 |    3

|  2 |  TABLE ACCESS BY GLOBAL INDEX ROWID| T       |    2 |  188 |    3

|* 3 |   INDEX RANGE SCAN                | IDX_T_ID |    2 |      |    1

--------------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

  3 - access("OBJECT_ID"=1000)

15 rows selected

 

 

Global索引使用对应的Table Access By Global Index Rowid,而Local索引使用对应Table Access By Local Index Rowid

 

从上面的执行计划看,进行了Partition Range Single操作,属于分区裁剪动作。在SQL中,没有涉及到owner的问题,所以这个剪裁动作必然是针对索引而言的。

 

 

SQL> explain plan for select * from t where object_id=1000 and wner='SCOTT';

 

Explained

 

SQL> select * from table(dbms_xplan.display);

 

PLAN_TABLE_OUTPUT

--------------------------------------------------------------------------------

Plan hash value: 2862326821

--------------------------------------------------------------------------------

| Id | Operation                          | Name    | Rows | Bytes | Cost (%

--------------------------------------------------------------------------------

|  0 | SELECT STATEMENT                   |         |    1 |   85 |    3

|  1 | PARTITIONRANGESINGLE            |         |    1 |   85 |    3

|* 2 |  TABLE ACCESS BY GLOBAL INDEX ROWID| T       |    1 |   85 |    3

|* 3 |   INDEX RANGE SCAN                | IDX_T_ID |    1 |      |    1

--------------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

  2 - filter("OWNER"='SCOTT')

  3 - access("OBJECT_ID"=1000)

 

16 rows selected

 

SQL> explain plan for select * from t where object_id=1000 and wner='SYS';

 

Explained

 

SQL> select * from table(dbms_xplan.display);

 

PLAN_TABLE_OUTPUT

--------------------------------------------------------------------------------

Plan hash value: 2862326821

--------------------------------------------------------------------------------

| Id | Operation                          | Name    | Rows | Bytes | Cost (%

--------------------------------------------------------------------------------

|  0 | SELECT STATEMENT                   |         |    2 |  186 |    3

|  1 | PARTITIONRANGESINGLE            |         |    2 |  186 |    3

|* 2 |  TABLE ACCESS BY GLOBAL INDEX ROWID| T       |    2 |  186 |    3

|* 3 |   INDEX RANGE SCAN                | IDX_T_ID |    1 |      |    1

--------------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

  2 - filter("OWNER"='SYS')

  3 - access("OBJECT_ID"=1000)

 

16 rows selected

 

 

上面两个执行计划,显然是利用索引分区的裁剪,先定位到具体的索引分区,之后进行owner条件查询筛选。

 

 

4、非分区表的情况

 

并不是只有分区表才能对应分区索引。如果一个非分区表有需要,是可以将其索引分区的。

 

 

SQL> create table t as select * from dba_objects;

Table created

 

SQL> insert into t select * from t;

53295 rows inserted

 

SQL> commit;

Commit complete

 

SQL> create index idx_t_id on t(object_id)

 2 global partition by range(object_id)

 3 (

 4    partition t_list_01 values less than (30000),

 5    partition t_list_02 values less than (70000),

 6    partition t_list_other values less than (maxvalue)

 7 );

 

Index created

 

 

普通数据表T,对应分区数据索引。我们观察一下结果情况。

 

 

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

PL/SQL procedure successfully completed

 

 

SQL> select segment_name, partition_name,segment_type, bytes from dba_segments where wner='SCOTT' and segment_name='IDX_T_ID';

 

SEGMENT_NAME    PARTITION_NAME     SEGMENT_TYPE           BYTES

-------------------- ------------------------------ ------------------ ----------

IDX_T_ID            T_LIST_01        INDEX PARTITION      2097152

IDX_T_ID            T_LIST_02        INDEX PARTITION       851968

IDX_T_ID            T_LIST_OTHER   INDEX PARTITION       196608

 

 

三个关键SQL,执行计划如下:

 

 

SQL> explain plan for select * from t where object_id=1000;

Explained

 

SQL> select * from table(dbms_xplan.display);

 

PLAN_TABLE_OUTPUT

--------------------------------------------------------------------------------

Plan hash value: 381899048

--------------------------------------------------------------------------------

| Id | Operation                   | Name    | Rows | Bytes | Cost (%CPU)| T

--------------------------------------------------------------------------------

|  0 | SELECT STATEMENT            |         |    2 |  188 |    3  (0)| 0

|  1 | PARTITIONRANGESINGLE     |         |    2 |  188 |    3  (0)| 0

|  2 |  TABLE ACCESS BY INDEX ROWID| T       |    2 |  188 |    3  (0)| 0

|* 3 |   INDEX RANGE SCAN         | IDX_T_ID |    2 |      |    1  (0)| 0

--------------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

  3 - access("OBJECT_ID"=1000)

 

15 rows selected

 

SQL> explain plan for select * from t where object_id=1000 and wner='SCOTT';

Explained

 

SQL> select * from table(dbms_xplan.display);

 

PLAN_TABLE_OUTPUT

--------------------------------------------------------------------------------

Plan hash value: 381899048

--------------------------------------------------------------------------------

| Id | Operation                   | Name    | Rows | Bytes | Cost (%CPU)| T

--------------------------------------------------------------------------------

|  0 | SELECT STATEMENT            |         |    1 |   94 |    3  (0)| 0

|  1 | PARTITIONRANGESINGLE     |         |    1 |   94 |    3  (0)| 0

|* 2 |  TABLE ACCESS BY INDEX ROWID| T       |    1 |   94 |    3  (0)| 0

|* 3 |   INDEX RANGE SCAN         | IDX_T_ID |    2 |      |    1  (0)| 0

--------------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

  2 - filter("OWNER"='SCOTT')

  3 - access("OBJECT_ID"=1000)

 

16 rows selected

 

SQL> explain plan for select * from t where object_id=1000 and wner='SYS';

 

Explained

 

SQL> select * from table(dbms_xplan.display);

 

PLAN_TABLE_OUTPUT

--------------------------------------------------------------------------------

Plan hash value: 381899048

--------------------------------------------------------------------------------

| Id | Operation                   | Name    | Rows | Bytes | Cost (%CPU)| T

--------------------------------------------------------------------------------

|  0 | SELECT STATEMENT            |         |    1 |   94 |    3  (0)| 0

|  1 | PARTITIONRANGESINGLE     |         |    1 |   94 |    3  (0)| 0

|* 2 |  TABLE ACCESS BY INDEX ROWID| T       |    1 |   94 |    3  (0)| 0

|* 3 |   INDEX RANGE SCAN         | IDX_T_ID |    2 |      |    1  (0)| 0

--------------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

  2 - filter("OWNER"='SYS')

  3 - access("OBJECT_ID"=1000)

 

16 rows selected

 

SQL> explain plan for select * from t where object_id=1000 and wner='SCOTT';

 

Explained

 

SQL> select * from table(dbms_xplan.display);

 

PLAN_TABLE_OUTPUT

--------------------------------------------------------------------------------

Plan hash value: 381899048

--------------------------------------------------------------------------------

| Id | Operation                   | Name    | Rows | Bytes | Cost (%CPU)| T

--------------------------------------------------------------------------------

|  0 | SELECT STATEMENT            |         |    1 |   94 |    3  (0)| 0

|  1 | PARTITIONRANGESINGLE     |         |    1 |   94 |    3  (0)| 0

|* 2 |  TABLE ACCESS BY INDEX ROWID| T       |    1 |   94 |    3  (0)| 0

|* 3 |   INDEX RANGE SCAN         | IDX_T_ID |    2 |      |    1  (0)| 0

--------------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

  2 - filter("OWNER"='SCOTT')

  3 - access("OBJECT_ID"=1000)

 

16 rows selected

 

 

索引分区裁剪、内部索引回表操作在上面的执行计划中体现的很明显。

 

5、结论

 

分治是一种古老的考虑方法,将复杂问题进行简化,大面积的削减备选集合,是很多现代算法和现代系统设计的核心要义。

 

分区索引是我们在设计分区表中不能回避的问题。针对不同的应用场景,选择合适的分区索引,才能做到真正的设计优化。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值