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

上篇(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的作用和效果。

 

 

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/17203031/viewspace-712989/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/17203031/viewspace-712989/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值