Oracle 基础篇 --- 索引选项

###4.4 选项数据库

####4.4.1 惟一索引oracle

确保每一个索引值都是惟一的.app

主键、惟一键与惟一索引的区别dom

通常,咱们看到术语“索引”和“键”交换使用,但实际上这两个是不一样的。索引是存储在数据库中的一个物理结构,键纯粹是一个逻辑概念。键表明建立来实施业务规则的完整性约束。索引和键的混淆一般是因为数据库使用索引来实施完整性约束。ide

主键约束、惟一索引的区别:函数

SQL> create table test(id int, name varchar2(20), constraint pk_id primary key(id));

Table created.

SQL> select constraint_name, constraint_type from user_constraints where table_name = 'TEST';

CONSTRAINT_NAME                C
------------------------------ -
PK_ID                          P

#在test表中,咱们指定了ID列做为主键,Oracle数据库会自动建立一个同名的惟一索引:

SQL> select index_name, index_type,uniqueness from user_indexes where table_name = 'TEST';

INDEX_NAME           INDEX_TYPE           UNIQUENES
-------------------- -------------------- ---------
PK_ID                NORMAL               UNIQUE

#此时,若是咱们再试图在ID列上建立一个惟一索引,Oracle会报错,由于该列上已经存在一个惟一索引:

SQL> create unique index ind_test_uk on test(id);
create unique index ind_test_uk on test(id)
                                        *
ERROR at line 1:
ORA-01408: such column list already indexed


SQL> create index ind_test_uk on test(id);
create index ind_test_uk on test(id)
                                 *
ERROR at line 1:
ORA-01408: such column list already indexed

惟一键约束、惟一索引的区别:oop

SQL> drop table test purge;

Table dropped.

SQL> create table test(
  2  id int,
  3  name varchar2(20),
  4  constraint uk_test unique(id));

Table created.

SQL> select constraint_name, constraint_type 
from user_constraints 
where table_name = 'TEST';

CONSTRAINT_NAME                C
------------------------------ -
UK_TEST                        U

SQL> select index_name, index_type, uniqueness
  2  from user_indexes
  3  where table_name = 'TEST';

INDEX_NAME           INDEX_TYPE           UNIQUENES
-------------------- -------------------- ---------
UK_TEST              NORMAL               UNIQUE

#Oracle一样自动建立了一个同名的惟一索引,并且也不容许再在此列上建立惟一索引或非惟一索引。

主键约束要求列值非空(NOT NULL),那么惟一键约束是否也要求非空呢?性能

SQL> insert into test values(1, 'sally');

1 row created.

SQL> insert into test values(null, 'Tony');

1 row created.

SQL> commit;

Commit complete.

SQL> select * from test;

        ID NAME
---------- --------------------
         1 sally
           Tony

#从实验结果来看,
惟一键约束并无非空要求。
惟一索引对列值非空不作要求。

键约束或者惟一键约束失效,Oracle自动建立的惟一索引是否会受到影响?测试

SQL> drop table test purge;

Table dropped.

create table test(
  id int,
  name varchar2(20),
  constraint uk_test unique(id));

Table created.

SQL> select index_name, index_type, uniqueness, status from user_indexes where table_name = 'TEST';

INDEX_NAME           INDEX_TYPE           UNIQUENES STATUS
-------------------- -------------------- --------- --------
UK_TEST              NORMAL               UNIQUE    VALID

SQL> alter table test disable constraint uk_test;

Table altered.

SQL> select index_name, index_type, uniqueness, status from user_indexes where table_name = 'TEST';

no rows selected

SQL> alter table test enable constraint uk_test;

Table altered.

SQL> select index_name, index_type, uniqueness, status from user_indexes where table_name = 'TEST';

INDEX_NAME           INDEX_TYPE           UNIQUENES STATUS
-------------------- -------------------- --------- --------
UK_TEST              NORMAL               UNIQUE    VALID

#当主键约束或者惟一键约束失效时,Oracle会标记隐式建立的惟一索引为删除状态。

先建立惟一索引,再建立主键或者惟一键约束,状况又会怎样呢?fetch

SQL> drop table test purge;

Table dropped.

SQL> create table test(
  2  id int,
  3  name varchar(20));

Table created.

SQL> create unique index idx_test_id on test(id);

Index created.

SQL> select index_name, index_type, uniqueness, status
  2  from user_indexes
  3  where table_name = 'TEST';

INDEX_NAME           INDEX_TYPE           UNIQUENES STATUS
-------------------- -------------------- --------- --------
IDX_TEST_ID          NORMAL               UNIQUE    VALID

SQL> alter table test add constraint uk_test unique(id);

Table altered.

SQL> select index_name, index_type, uniqueness, status
  2  from user_indexes
  3  where table_name = 'TEST';

INDEX_NAME           INDEX_TYPE           UNIQUENES STATUS
-------------------- -------------------- --------- --------
IDX_TEST_ID          NORMAL               UNIQUE    VALID

SQL> select constraint_name, constraint_type
  2  from user_constraints
  3  where table_name = 'TEST';

CONSTRAINT_NAME                C
------------------------------ -
UK_TEST                        U

SQL> alter table test disable constraint uk_test;

Table altered.

SQL> select constraint_name, constraint_type, status
  2  from user_constraints
  3  where table_name = 'TEST';

CONSTRAINT_NAME                C STATUS
------------------------------ - --------
UK_TEST                        U DISABLED

SQL> select index_name, index_type, uniqueness, status
  2  from user_indexes
  3  where table_name = 'TEST';

INDEX_NAME           INDEX_TYPE           UNIQUENES STATUS
-------------------- -------------------- --------- --------
IDX_TEST_ID          NORMAL               UNIQUE    VALID

####4.4.2 反向关键字索引

“反向关键字索引”会按相反顺序存储索引值的字节。这能够减小索引中特定热点的活 动量。若是许多用户正按同一顺序处理数据,那么在任何给定时刻,关键字值的前缀部分 (当前正在处理的)是很是接近的值。所以,在索引结构的该区域中会发生大量的活动。 为反向字节样式的关键字值创建索引后,反向关键字索引可在索引结构中分散这些活动。

REVERSE索引也是一种B树索引,但它物理上将按照列顺序保存的每一个索引键值进行了反转。例如,索引键是20,用16进制存储这个标准B树索引键的两个字节是C1,15,那么反向索引存储的字节就是15,C1。

反向索引主要解决的是叶子块的争用问题。在RAC中,这个问题更加明显,可能多实例反复修改同一个块。举个例子,在一张按照主键顺序存储的表中,一个实例增长记录20,另外一个增长21,这两个值的键存储于同一个索引叶子块的左右两侧。

在反向索引中,插入操做会被反序字节排列分发到索引的所有叶子键中。就像上面的例子,20和21两个键,在标准键索引中,他们应该是相邻的,但在反向索引中,他们会被分开存储。所以按顺序键插入操做的IO会更加平均。

由于索引上的数据不是按照列存储的顺序,反向索引会禁止一些案例中能够用到的索引范围扫描。例如,若是一个用户查询ID值大于20的记录,那么数据库不能从包含这个ID的块开始查找,而是从全部的叶子块。

这种索引的设计目的是消除插入操做的索引热点。对于插入的性能提高有帮助,但也是有限的,由于数据库不能使用索引范围扫描了。

Sometimes, using a reverse-key index can make an OLTP Oracle Real Application Clusters application faster. For example, keeping the index of mail messages in an e-mail application: some users keep old messages, and the index must maintain pointers to these as well as to the most recent.

SQL> create table t(
  2  a number,
  3  b varchar(10),
  4  c date );

Table created.

SQL> begin
  2  for i int 1..1000 loop
  3  insert into t values(i, 'Test', sysdate);
  4  end loop;
  5  commit;
  6  end;
  7  /

SQL> create index ind_t_rev on t(a,b,c) reverse;

Index created.

SQL> select index_name, index_type from user_indexes where table_name = 'T';

INDEX_NAME                     INDEX_TYPE
------------------------------ ---------------------------
IND_T_REV                      NORMAL/REV

#Using the reverse key arrangement eliminates the ability to run an index range scanning query on the index. 
Because lexically adjacent keys are not stored next to each other in a reverse-key index, 
only fetch-by-key or full-index (table) scans can be performed.

SQL> select * from t where a = 1000;

------------------------------------------------------------------------------
| Id  | Operation        | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------
|   0 | SELECT STATEMENT |           |     1 |    29 |     2   (0)| 00:00:01 |
|*  1 |  INDEX RANGE SCAN| IND_T_REV |     1 |    29 |     2   (0)| 00:00:01 |
------------------------------------------------------------------------------

SQL> select count(*) from t where trunc(c) = to_date('24-JUL-15','DD-MM-YY');

---------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |     1 |     9 |     3   (0)| 00:00:01 |
|   1 |  SORT AGGREGATE    |      |     1 |     9 |            |          |
|*  2 |   TABLE ACCESS FULL| T    |  1000 |  9000 |     3   (0)| 00:00:01 |
---------------------------------------------------------------------------

#取消reverse
SQL> alter index ind_t_rev rebuild noreverse;

Index altered.

SQL> select index_name, index_type from user_indexes where table_name = 'T';

INDEX_NAME                     INDEX_TYPE
------------------------------ ---------------------------
IND_T_REV                      NORMAL

####4.4.3 组合索引

在Oracle中能够建立组合索引,即同时包含两个或两个以上列的索引。

  • 当使用基于规则的优化器(RBO)时,只有当组合索引的前导列出如今SQL语句的where子句中时,才会使用到该索引;
  • 在使用Oracle9i以前的基于成本的优化器(CBO)时, 只有当组合索引的前导列出如今SQL语句的where子句中时,才可能会使用到该索引,这取决于优化器计算的使用索引的成本和使用全表扫描的成本,Oracle会自动选择成本低的访问路径(请见下面的测试1和测试2);
  • 从Oracle9i起,Oracle引入了一种新的索引扫描方式——索引跳跃扫描(index skip scan),这种扫描方式只有基于成本的优化器(CBO)才能使用。这样,当SQL语句的where子句中即便没有组合索引的前导列,而且索引跳跃扫描的成本低于其余扫描方式的成本时,Oracle就会使用该方式扫描组合索引(请见下面的测试3);
  • Oracle优化器有时会作出错误的选择,由于它再“聪明”,也不如咱们SQL语句编写人员更清楚表中数据的分布,在这种状况下,经过使用提示(hint),咱们能够帮助Oracle优化器做出更好的选择(请见下面的测试4)。

建立测试表T

#T表建立
SQL> create table t as select * from all_objects;

Table created.

#数据分布
SQL> select object_type, count(*) from t group by object_type;

OBJECT_TYPE           COUNT(*)
------------------- ----------
EDITION                      1
INDEX PARTITION            512
TABLE SUBPARTITION          32
CONSUMER GROUP               2
SEQUENCE                   245
SYNONYM                  27889
JOB                         15
......

SQL> select count(*) from t;

  COUNT(*)
----------
     74051

#建立复合索引
SQL> create index indx_t on t(object_type,object_name);

Index created.

SQL> select INDEX_NAME, INDEX_type from user_indexes where table_name = 'T';

INDEX_NAME      INDEX_TYPE
--------------- ---------------
INDX_T          NORMAL

SQL> select index_name, table_name, column_name from user_ind_columns where TABLE_NAME = 'T';

INDEX_NAME      TABLE_NAME      COLUMN_NAME
--------------- --------------- ------------------------------
INDX_T          T               OBJECT_TYPE
INDX_T          T               OBJECT_NAME

SQL> analyze table t compute statistics
  2  for table
  3  for all indexes
  4  for all indexed columns
  5  /

Table analyzed.

测试一:使用了组合索引的前导列而且访问了表中的少许记录

SQL> set autotrace traceonly

SQL> select * from t where object_type='JOB';

15 rows selected.


Execution Plan
----------------------------------------------------------
Plan hash value: 723869532

--------------------------------------------------------------------------------------
| Id  | Operation                   | Name   | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |        |    15 |  1500 |    14   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T      |    15 |  1500 |    14   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | INDX_T |    15 |       |     3   (0)| 00:00:01 |
--------------------------------------------------------------------------------------

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

   2 - access("OBJECT_TYPE"='JOB')


Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
         16  consistent gets
          0  physical reads
          0  redo size
       2980  bytes sent via SQL*Net to client
        524  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
         15  rows processed
#正如咱们所指望的,因为使用了组合索引的前导列而且访问了表中的少许记录,Oracle明智地选择了索引扫描。

测试二:使用了组合索引的前导列,是因为访问了表中的大量数据

SQL> select * from t where object_type='SYNONYM';

27889 rows selected.


Execution Plan
----------------------------------------------------------
Plan hash value: 1601196873

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      | 27889 |  2723K|   297   (1)| 00:00:04 |
|*  1 |  TABLE ACCESS FULL| T    | 27889 |  2723K|   297   (1)| 00:00:04 |
--------------------------------------------------------------------------

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

   1 - filter("OBJECT_TYPE"='SYNONYM')


Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
       2894  consistent gets
          0  physical reads
          0  redo size
    1381826  bytes sent via SQL*Net to client
      20973  bytes received via SQL*Net from client
       1861  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
      27889  rows processed

# 很明显,即便使用了组合索引的前导列,可是因为访问了表中的大量数据,Oracle选择了不使用索引而直接使用全表扫描,由于优化器认为全表扫描的成本更低,但事实是否是真的这样的?咱们经过增长提示(hint)来强制它使用索引来看看:

SQL> select /*+ index(T indx_t) */ * from t where object_type = 'SYNONYM';

27889 rows selected.


Execution Plan
----------------------------------------------------------
Plan hash value: 723869532

--------------------------------------------------------------------------------------
| Id  | Operation                   | Name   | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |        | 27889 |  2723K| 20012   (1)| 00:04:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T      | 27889 |  2723K| 20012   (1)| 00:04:01 |
|*  2 |   INDEX RANGE SCAN          | INDX_T | 27889 |       |   173   (0)| 00:00:03 |
--------------------------------------------------------------------------------------

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

   2 - access("OBJECT_TYPE"='SYNONYM')


Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
      24661  consistent gets
          0  physical reads
          0  redo size
    3236139  bytes sent via SQL*Net to client
      20973  bytes received via SQL*Net from client
       1861  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
      27889  rows processed
#从以上结果能够看出,在访问大量数据的状况下,使用索引确实会致使更高的执行成本,这从statistics部分的逻辑读取数(consistent gets)就能够看出,使用索引致使的逻辑读取数是不使用索引致使的逻辑读的10倍还多。所以,Oracle明智地选择了全表扫描而不是索引扫描。

测试三: where子句中没有索引前导列的状况

SQL> select * from t where object_name = 'DBA_TAB_COLS';


Execution Plan
----------------------------------------------------------
Plan hash value: 2722864248

--------------------------------------------------------------------------------------
| Id  | Operation                   | Name   | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |        |     2 |   200 |    43   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T      |     2 |   200 |    43   (0)| 00:00:01 |
|*  2 |   INDEX SKIP SCAN           | INDX_T |     2 |       |    41   (0)| 00:00:01 |
--------------------------------------------------------------------------------------

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

   2 - access("OBJECT_NAME"='DBA_TAB_COLS')
       filter("OBJECT_NAME"='DBA_TAB_COLS')


Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
         35  consistent gets
          0  physical reads
          0  redo size
       1753  bytes sent via SQL*Net to client
        524  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          2  rows processed

因为只查询了2条数据,即便没有使用前导列,Oracle正确地选择了索引跳跃扫描。咱们再来看看若是不使用索引跳跃扫描,该语句的成本:

SQL> select /*+ no_index(t indx_t)*/ * from t where object_name = 'DBA_TAB_COLS';


Execution Plan
----------------------------------------------------------
Plan hash value: 1601196873

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |     2 |   200 |   296   (1)| 00:00:04 |
|*  1 |  TABLE ACCESS FULL| T    |     2 |   200 |   296   (1)| 00:00:04 |
--------------------------------------------------------------------------

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

   1 - filter("OBJECT_NAME"='DBA_TAB_COLS')


Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
       1060  consistent gets
          0  physical reads
          0  redo size
       1747  bytes sent via SQL*Net to client
        524  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          2  rows processed

测试四:不选择使用索引的状况

SQL> select * from t where object_name like 'DE%';

101 rows selected.


Execution Plan
----------------------------------------------------------
Plan hash value: 1601196873

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |   267 | 26700 |   296   (1)| 00:00:04 |
|*  1 |  TABLE ACCESS FULL| T    |   267 | 26700 |   296   (1)| 00:00:04 |
--------------------------------------------------------------------------

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

   1 - filter("OBJECT_NAME" LIKE 'DE%')


Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
       1065  consistent gets
          0  physical reads
          0  redo size
       8012  bytes sent via SQL*Net to client
        590  bytes received via SQL*Net from client
          8  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
        101  rows processed


#此次只选择了101条数据,跟表T中总的数据量74051条相比,显然只是很小的一部分,可是Oracle仍是选择了全表扫描,有1065个逻辑读。这种状况下,若是咱们强制使用索引,状况会怎样呢?

SQL> select /*+ index(t indx_t)*/ * from t where object_name like 'DE%';

101 rows selected.


Execution Plan
----------------------------------------------------------
Plan hash value: 2722864248

--------------------------------------------------------------------------------------
| Id  | Operation                   | Name   | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |        |   267 | 26700 |   455   (0)| 00:00:06 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T      |   267 | 26700 |   455   (0)| 00:00:06 |
|*  2 |   INDEX SKIP SCAN           | INDX_T |   267 |       |   265   (0)| 00:00:04 |
--------------------------------------------------------------------------------------

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

   2 - access("OBJECT_NAME" LIKE 'DE%')
       filter("OBJECT_NAME" LIKE 'DE%')


Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
        119  consistent gets
          0  physical reads
          0  redo size
      11862  bytes sent via SQL*Net to client
        590  bytes received via SQL*Net from client
          8  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
        101  rows processed

#经过添加提示(hint),咱们强制Oracle使用了索引扫描(INDEX SKIP SCAN),执行了119个逻辑读,比使用全表扫描的时候少。

#因而可知,Oracle优化器有时会作出错误的选择,由于它再“聪明”,也不如咱们SQL语句编写人员更清楚表中数据的分布,在这种状况下,经过使用提示(hint),咱们能够帮助Oracle优化器做出更好的选择。

####4.4.4 基于函数的索引

使用Oracle函数索引,无疑是提升查询效率的有效方法之一。谈到任何对列的操做均可能致使全表扫描,例如:

SQL> select employee_id, first_name from employees where substr(first_name,1,2) = 'Sa';

-------------------------------------------------------------------------------
| Id  | Operation	  | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |	      |     1 |    11 |     3	(0)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| EMPLOYEES |     1 |    11 |     3	(0)| 00:00:01 |
-------------------------------------------------------------------------------

可是这种查询在客服系统又常用,咱们能够建立一个带有substr函数的基于Oracle函数索引,

create index emp_fname_substr on employees(substr(first_name, 1, 2));

SQL> select index_name, index_type from user_indexes where table_name = 'EMPLOYEES';

INDEX_NAME		       INDEX_TYPE
------------------------------ ---------------------------
EMP_LAST_NAME_IDX	       NORMAL
EMP_PHONE_IX		       NORMAL
EMP_FNAME_SUBSTR	       FUNCTION-BASED NORMAL

SQL> select employee_id, first_name from employees where substr(first_name,1,2) = 'Sa';

-------------------------------------------------------------------------------------------
| Id  | Operation		    | Name	       | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT	    |		       |     1 |    14 |     2	 (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| EMPLOYEES |     1 |    14 |     2	 (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN | EMP_FNAME_SUBSTR |     1 |       |     1	 (0)| 00:00:01 |
-------------------------------------------------------------------------------------------

这样在执行上面的查询语句时,这个基于函数的索引将排上用场,执行计划将是(INDEX RANGE SCAN)。 上面的例子中,咱们建立了基于函数的索引,可是若是执行下面的查询:

SQL> select employee_id, first_name from employees where substr(first_name,1,1) = 'S';

13 rows selected.


Execution Plan
----------------------------------------------------------
Plan hash value: 1445457117

-------------------------------------------------------------------------------
| Id  | Operation	  | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |	      |     1 |    11 |     3	(0)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| EMPLOYEES |     1 |    11 |     3	(0)| 00:00:01 |
-------------------------------------------------------------------------------

获得的执行计划将仍是(TABLE ACCESS FULL),由于只有当数据列可以等式匹配时,基于函数的索引才能生效,这样对于这种索引的计划和维护的要求都很高。请注意,向表中添加索引是很是危险的操做,由于这将致使许多查询执行计划的变动。然而,若是咱们使用基于函数的索引就不会产生这样的问题,由于Oracle只有在查询使用了匹配的内置函数时才会使用这种类型的索引。


####4.4.5 压缩索引

oracle 索引压缩(key compression)是oracle 9i 中引入的一项新特性。该特性能够压缩索引或者索引组织表中的重复键值,从而节省存储空间。非分区的unique 索引和non-unique(至少两列)索引都可以被压缩。bitmap 索引不可以进行压缩。

在oracle 索引压缩中有几个比较纠结的术语,须要说明一下。索引压缩是经过将索引中的键值拆分红两部分实现的,也就是grouping piece 也称做prefix 和 unique piece 也称做suffix 。grouping piece 是用来压缩的被unique piece 共享的部分。若是键值不能提供unique piece,那么oracle 将会使用rowid 来惟一标识。只有B-tree 索引的叶子节点可以被压缩,分支节点不可以被压缩。索引压缩是在单个block 中完成的,不可以跨blocks进行索引压缩。grouping piece (prefix) 和 unique piece (suffix) 存储在同一个索引 block 中。

具体prefix 和 suffix 是怎么划分的呢?默认prefix 长度等于索引列的数量减去1。固然咱们能够人为控制prefix 的长度,非惟一索引的最大prefix 长度等于索引列的数量。惟一索引的最大prefix 长度等于索引列的数量减去1。好比,假设索引有三个列: 默认的时候:prefix (column1,column2) suffix (column3) 若是有如下几组键值(1,2,3),(1,2,4),(1,2,7),(1,3,5),(1,3,4),(1,4,4) 那么在prefix中重复的(1,2),(1,3) 将会被压缩至保留一份。

索引压缩适合于那些键值重复率高的索引,这样才可以达到压缩键值,节省存储空间目的。索引压缩之后一个索引块能够存放更多的键值,这样当进行full index scan,full fast index scan 的时候IO性能会更好,可是CPU的负载会增长,至于整体的性能就要看IO性能的提升和CPU负载增长那个是主要方面了。我不认为索引压缩性能老是提升的,更多的意义在于节省存储空间,减小IO时间。

SQL> create table objects1 as select object_id, object_name from dba_objects;

SQL> create table objects2 as select 100 object_id, object_name from dba_objects;

SQL> create table objects3 as select object_id, object_name from dba_objects;

SQL> create index objects1_idx on objects1(object_id) compress 1;

Index created.

SQL> create index objects2_inx on objects2(object_id) compress 1;

Index created.

SQL> create index objects3_inx on objects3(object_id);

Index created.

SQL> select index_name, compression, leaf_blocks
     from user_indexes
     where index_name in ('OBJECTS1_IDX','OBJECTS2_INX','OBJECTS3_INX');

INDEX_NAME                     COMPRESS LEAF_BLOCKS
------------------------------ -------- -----------
OBJECTS1_IDX                   ENABLED          230
OBJECTS2_INX                   ENABLED          116
OBJECTS3_INX                   DISABLED         167



SQL> select object_id,object_name from objects1 where object_id = 100;

--------------------------------------------------------------------------------------------
| Id  | Operation                   | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |              |     1 |    29 |     2   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| OBJECTS1     |     1 |    29 |     2   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | OBJECTS1_IDX |     1 |       |     1   (0)| 00:00:01 |
--------------------------------------------------------------------------------------------

SQL> select object_id,object_name from objects2 where object_id = 100;

------------------------------------------------------------------------------
| Id  | Operation         | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |          | 75203 |  1982K|    98   (2)| 00:00:02 |
|*  1 |  TABLE ACCESS FULL| OBJECTS2 | 75203 |  1982K|    98   (2)| 00:00:02 |
------------------------------------------------------------------------------

SQL> select object_id,object_name from objects3 where object_id = 100;

--------------------------------------------------------------------------------------------
| Id  | Operation                   | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |              |     1 |    29 |     2   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| OBJECTS3     |     1 |    29 |     2   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | OBJECTS3_INX |     1 |       |     1   (0)| 00:00:01 |
--------------------------------------------------------------------------------------------

咱们能够看到对于objects1 和 objects3 由于object_id 都是惟一的,因此没有压缩的空间,压缩之后索引反而占用了更大的空间,还不如不压缩。而objects2 中 object_id 都是重复的压缩效果明显。

除了建立的时候进行索引压缩,还能够在rebuild index 的时候指定索引压缩和解压缩。

SQL> alter index objects1_idx rebuild nocompress;
Index altered.
SQL> alter index objects1_idx rebuild compress;
Index altered.

注:压缩也是会引入存储开销的,只是不少时候压缩节省的空间比压缩须要的存储开销更大,因此压缩之后总体的存储开销减少了。 compress 后面接的数字表示的是prefix 的深度,也就是须要用来压缩的columns 的数量。


####4.4.6 顺序索引

The DESC keyword on the CREATE INDEX statement is no longer ignored. It specifies that the index should be created in descending order. Indexes on character data are created in descending order of the character values in the database character set. Neither this, nor the ASC keyword, may be specified for a domain index. DESC cannot be specified for a bitmapped index.:

# would benefit from an index like this:
CREATE INDEX c_id_desc ON Citites(city_id DESC)
SELECT * FROM Cities ORDER BY city_id DESC

# would benefit from an index like this:
CREATE INDEX f_miles_desc on Flights(miles DESC)
SELECT MAX(miles) FROM Flight

# would benefit from an index like this:
CREATE INDEX arrival_time_desc ON Flights(dest_airport, arrive_time DESC)
SELECT * FROM Flights WHERE dest_airport = 'LAX'
ORDER BY ARRIVAL DESC
SQL> create table t_objects as 
  2  select object_name, object_id, created, owner
  3  from all_objects;

SQL> select count(*) from t_objects;

  COUNT(*)
----------
     74101

#建立升序索引
SQL> create index t_idx_1 on t_objects (object_name, owner);   ---the usual index.

SQL> select index_name, index_type from user_indexes where table_name = 'T_OBJECTS';

INDEX_NAME           INDEX_TYPE
-------------------- ---------------
T_IDX_1              NORMAL

SQL> select index_name, table_name, column_name, descend from user_ind_columns where index_name = 'T_IDX_1';

INDEX_NAME           TABLE_NAME           COLUMN_NAME          DESC
-------------------- -------------------- -------------------- ----
T_IDX_1              T_OBJECTS            OBJECT_NAME          ASC
T_IDX_1              T_OBJECTS            OWNER                ASC

#the database does not use descending indexes until you first analyze the index and the table on which the index is defined
SQL> select * from t_objects
  2  where object_name between 'Y' and 'Z'
  3  order by object_name asc, owner asc;

--------------------------------------------------------------------------------
| Id  | Operation          | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |           |  1004 | 43172 |   141   (2)| 00:00:02 |
|   1 |  SORT ORDER BY     |           |  1004 | 43172 |   141   (2)| 00:00:02 |
|*  2 |   TABLE ACCESS FULL| T_OBJECTS |  1004 | 43172 |   140   (1)| 00:00:02 |
--------------------------------------------------------------------------------

SQL> analyze table t_objects
  2  compute statistics
  3  for all columns
  4  for all indexes;

Table analyzed.

SQL> select * from t_objects
     where object_name between 'Y' and 'Z'
     order by object_name asc, owner asc;

-----------------------------------------------------------------------------------------
| Id  | Operation                   | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |           |    82 |  3280 |    43   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T_OBJECTS |    82 |  3280 |    43   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | T_IDX_1   |    82 |       |     3   (0)| 00:00:01 |
-----------------------------------------------------------------------------------------

SQL> select * from t_objects
     where object_name between 'Y' and 'Z'
     order by object_name desc, owner desc;

------------------------------------------------------------------------------------------
| Id  | Operation                    | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |           |    82 |  3280 |    43   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID | T_OBJECTS |    82 |  3280 |    43   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN DESCENDING| T_IDX_1   |    82 |       |     3   (0)| 00:00:01 |
------------------------------------------------------------------------------------------

#建立降序索引
SQL> create index t_inx_1 on t_objects(object_name desc, owner desc);

Index created.

SQL> select index_name, table_name, column_name, descend from user_ind_columns where index_name = 'T_INX_1';

INDEX_NAME           TABLE_NAME           COLUMN_NAME          DESC
-------------------- -------------------- -------------------- ----
T_INX_1              T_OBJECTS            SYS_NC00005$         DESC
T_INX_1              T_OBJECTS            SYS_NC00006$         DESC


SQL> select * from t_objects
     where object_name between 'Y' and 'Z'
     order by object_name asc, owner asc;

------------------------------------------------------------------------------------------
| Id  | Operation                    | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |           |    82 |  3280 |    29   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID | T_OBJECTS |    82 |  3280 |    29   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN DESCENDING| T_INX_1   |    47 |       |     5   (0)| 00:00:01 |
------------------------------------------------------------------------------------------

SQL> select * from t_objects
     where object_name between 'Y' and 'Z'
     order by object_name desc, owner desc;

-----------------------------------------------------------------------------------------
| Id  | Operation                   | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |           |    82 |  3280 |    29   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T_OBJECTS |    82 |  3280 |    29   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | T_INX_1   |    47 |       |     5   (0)| 00:00:01 |
-----------------------------------------------------------------------------------------
相关文章
相关标签/搜索