1.索引

1.1 索引

         索引是建立在表的一列或多个列上的辅助对象,目的是加快访问表中的数据;Oracle存储索引的数据结构是 B 树位图索引也是如此,只不过是叶子节点不同B数索引;索引由根节点、分支节点和叶子节点组成,上级索引块包含下级索引块的索引数据叶节点包含索引数据和确定行实际位置的rowid

1.1.1 索引特点: 

第一.通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。 

第二.可以大大加快数据的检索速度,这也是创建索引的最主要的原因。 

第三.可以加速表和表之间的连接,特别是在实现数据的参考完整性方面特别有意义。 

第四.在使用分组和排序子句进行数据检索时,同样可以显著减少查询中分组和排序的时间。 

第五.通过使用索引.可以在查询的过程中,使用优化隐藏器,提高系统的性能。 

1.1.2应该建索引列的特点:
  • 经常需要搜索的列上,可以加快搜索的速度; 
  • 在作为主键的列上,强制该列的唯一性和组织表中数据的排列结构; 
  • 在经常用在连接的列上,这些列主要是一些外键,可以加快连接的速度; 
  • 在经常需要根据范围进行搜索的列上创建索引,因为索引已经排序.其指定的范围是连续的; 
  • 在经常需要排序的列上创建索引,因为索引已经排序,这样查询可以利用索引的排序,加快排序查询时间; 
  • 在经常使用在WHERE子句中的列上面创建索引,加快条件的判断速度。 

1.2 目的

        当查询 返回的记录数排序表 < 40%非排序表  < 7%且表的碎片较多(频繁增加、删除)时可以加快查询速度减少I/O操作消除磁盘排序

1.3 分类

1.3.1 逻辑上

Single column/Concatenated 单行索引/多行索引
Unique/NonUnique 唯一索引/非唯一索引

1.3.2、物理上

B-tree B树索引
Bitmap 位图索引
REVERSE 反向索引
HASHHASH索引
Function-based基于函数的索引
Partitioned/NonPartitioned 分区索引/非分区索引
Domain 域索引

1.4 各种索引详解

1.4.1 B树索引

Oracle中最常见的索引类型是b-tree索引,也就是B-树索引,以其同名的计算科学结构命名。CREATE INDEX语句时,默认就是在创建b-tree索引。没有特别规定可用于任何情况。

(1)特点:

  1.   适合与大量的增、删、改(OLTP)
  2.   不能用包含OR操作符的查询;
  3.   适合高基数的列(唯一值多)(等值匹配)
  4.   典型的树状结构;
  5.   每个结点都是数据块;
  6.   大多都是物理上一层、两层或三层不定,逻辑上三层;
  7.   叶子块数据是排序的,从左向右递增;
  8.   在分支块和根块中放的是索引的范围(范围查找)

  索引列的值都存储在索引中。因此,可以建立一个组合(复合)索引,这些索引可以直接满足查询,而不用访问表。这就不用从表中检索数据,从而减少了I/O量。

1.4.2、位图索引

位图索引非常适合于决策支持系统(Decision Support System,DSS)和数据仓库,它们不应该用于通过事务处理应用程序访问的表。它们可以使用较少到中等基数(不同值的数量)的列访问非常大的表。尽管位图索引最多可达30个列,但通常它们都只用于少量的列。

  •         适合与决策支持系统;
  •   做UPDATE代价非常高;
  •   非常适合OR操作符的查询;
  •   基数比较少的时候才能建位图索引;

位图对于低基数(少量的不同值)列来说非常快,这是因为索引的尺寸相对于B树索引来说小了很多

位图索引在批处理(单用户)操作中加载表(插入操作)方面通常要比B树做得好,当多个会话同时向表中插入行时不应该使用位图索引,在大多数事务处理应用程序中都会发生这种情况。在一个查询中合并多个位图索引后,可以使性能显著提高。位图索引使用固定长度的数据类型要比可变长度的数据类型好。

1.4.3 反向索引

如果在创建B树索引的时候索引列的值比较密集,这样不能够  很好的利用系统的并行操作来提高查询效率,可以讲索引列的值逆向存贮,这样就可以打破数据额度集中度。

不可以将反转键索引与位图索引或索引组织表结合使用。因为不能对位图索引和索引组织表进行反转键处理。

1.4.4HASH索引

           使用HASH索引必须要使用HASH集群。建立一个集群或HASH集群的同时,也就定义了一个集群键。这个键告诉Oracle如何在集群上存储表。在存储数据时,所有与这个集群键相关的行都被存储在一个数据库块上。如果数据都存储在同一个数据库块上,并且将HASH索引作为WHERE子句中的确切匹配,Oracle就可以通过执行一个HASH函数和I/O来访问数据——而通过使用一个二元高度为4的B树索引来访问数据,则需要在检索数据时使用4个I/O。其中的查询是一个等价查询,用于匹配HASH列和确切的值。Oracle可以快速使用该值,基于HASH函数确定行的物理存储位置。

HASH索引可能是访问数据库中数据的最快方法,但它也有自身的缺点。集群键上不同值的数目必须在创建HASH集群之前就要知道。需要在创建HASH集群的时候指定这个值。低估了集群键的不同值的数字可能会造成集群的冲突(两个集群的键值拥有相同的HASH值)。这种冲突是非常消耗资源的。冲突会造成用来存储额外行的缓冲溢出,然后造成额外的I/O。如果不同HASH值的数目已经被低估,您就必须在重建这个集群之后改变这个值。

 ALTER CLUSTER命令不能改变HASH键的数目。HASH集群还可能浪费空间。如果无法确定需要多少空间来维护某个集群键上的所有行,就可能造成空间的浪费。如果不能为集群的未来增长分配好附加的空间,HASH集群可能就不是最好的选择。如果应用程序经常在集群表上进行全表扫描,HASH集群可能也不是最好的选择。由于需要为未来的增长分配好集群的剩余空间量,全表扫描可能非常消耗资源。

1.4.5函数索引

       可以在表中创建基于函数的索引。如果没有基于函数的索引,任何在列上执行了函数的查询都不能使用这个列的索引。

下面的查询就不能使用JOB列上的索引,除非它是基于函数的索引:

  • select * from emp where UPPER(job) = 'MGR';

下面的查询使用JOB列上的索引,但是它将不会返回JOB列具有Mgr或mgr值的行:

  • select * from emp where job = 'MGR';

可以创建这样的索引,允许索引访问支持基于函数的列或数据。可以对列表达式UPPER(job)创建索引,而不是直接在JOB列上建立索引,如:

create index EMP$UPPER_JOB on emp(UPPER(job));
尽管基于函数的索引非常有用,但在建立它们之前必须先考虑下面一些问题:

  • 能限制在这个列上使用的函数吗?如果能,能限制所有在这个列上执行的所有函数吗
  • 是否有足够应付额外索引的存储空间?
  • 在每列上增加的索引数量会对针对该表执行的DML语句的性能带来何种影响?
  • 基于函数的索引非常有用,但在实现时必须小心。在表上创建的索引越多,INSERT、UPDATE和DELETE语句的执行就会花费越多的时间。
1.4.6 分区索引和全局索引

        分区索引就是简单地把一个索引分成多个片断。通过把一个索引分成多个片断,可以访问更小的片断(也更快),并且可以把这些片断分别存放在不同的磁盘驱动器上(避免I/O问题)。B树和位图索引都可以被分区,而HASH索引不可以被分区。可以有好几种分区方法:表被分区而索引未被分区;表未被分区而索引被分区;表和索引都被分区。不管采用哪种方法,都必须使用基于成本的优化器。分区能够提供更多可以提高性能和可维护性的可能性

        本地分区索引和全局分区索引。每个类型都有两个子类型,有前缀索引无前缀索引。表各列上的索引可以有各种类型索引的组合。如果使用了位图索引,就必须是本地索引。索引分区最主要的原因是可以减少所需读取的索引的大小,另外把分区放在不同的表空间中可以提高分区的可用性和可靠性。全局索引只能是B树索引。Oracle在默认情况下不会维护全局分区索引。如果一个分区被截取、增加、分割、删除等.就必须重建全局分区索引

        在使用分区后的表和索引时,Oracle还支持并行查询和并行DML。这样就可以同时执行多个进程,从而加快处理这条语句。

        可以使用与表相同的分区键和范围界限来对本地索引分区。每个本地索引的分区只包含了它所关联的表分区的键和ROWID本地索引可以是B树或位图索引。如果是B树索引,它可以是唯一或不唯一的索引。

        索引支持分区独立性,这就意味着对于单独的分区,可以进行增加、截取、删除、分割、脱机等处理,而不用同时删除或重建索引。Oracle自动维护这些本地索引。本地索引分区还可以被单独重建,而其他分区不会受到影响。

1.4.7 域索引 Domain

        域索引实际为用户自定义索引,域索引主要对存储在数据库中的媒体,图像数据进行索引,这些数据在oracle中基本上以BLOB类型存储,不同的应用存储格式也不同,oracle不可能提供某一种现成的算法对这些数据进行索引,为了能够对这些类型数据快速访问,oracle提供了现成的接口函数,用户可以针对自己的数据格式实现这些接口函数,以达到对这些数据的快速访问。

1.5 建立索引

CREATE UNIQUE | BITMAP INDEX <schema>.<index_name>

      ON <schema>.<table_name>

           (<column_name> | <expression> ASC | DESC,
            <column_name> | <expression> ASC | DESC,...)
     TABLESPACE <tablespace_name>
     STORAGE <storage_settings>
     LOGGING | NOLOGGING
    COMPUTE STATISTICS
     NOCOMPRESS | COMPRESS<nn>
     NOSORT | REVERSE
     PARTITION | GLOBAL PARTITION<partition_setting>
UNIQUE | BITMAP:指定UNIQUE为唯一值索引,BITMAP为位图索引,省略为B-Tree索引。
<column_name> | <expression> ASC | DESC:可以对多列进行联合索引,当为expression时即“基于函数的索引”
TABLESPACE:指定存放索引的表空间(索引和原表不在一个表空间时效率更高)
STORAGE:可进一步设置表空间的存储参数
LOGGING | NOLOGGING:是否对索引产生重做日志(对大表尽量使用NOLOGGING来减少占用空间并提高效率)
COMPUTE STATISTICS:创建新索引时收集统计信息
NOCOMPRESS | COMPRESS<nn>:是否使用“键压缩”(使用键压缩可以删除一个键列中出现的重复值)
NOSORT | REVERSE:NOSORT表示与表中相同的顺序创建索引,REVERSE表示相反顺序存储索引值
PARTITION | NOPARTITION:可以在分区表和未分区表上对创建的索引进行分区
1.5.1、普通索引
  • create index index_text_txt on test(txt);
1.5.2 唯一索引
  • create unique index <index_name> on <table_name>(<coiumn_name>);
     
1.5.3 位图索引
  • create bitmap index <index_name> on <table_name>(<column_name>)
1.5.4 组合索引
  • create index <index_name> on <table_name>(<column_name1><column_name2>)
1.5.5 基于函数索引
  • create index <index_name> on <table_name>(upper(column_name))
1.5.6 反向键索引
  • create index <index_name> on <table_name>(column_name) reverse;
1.5.7 重置索引
  • alter index <index_name> rebuild;
1.5.8 删除索引
  • drop index <index_name>

1.6 索引失效细节

1.6.1 使用不等于操作符(<>, !=)

下面这种情况,即使在列dept_id有一个索引,查询语句仍然执行一次全表扫描

  • select * from dept where staff_num <> 1000;

通过把用 or 语法替代不等号进行查询,就可以使用索引,以避免全表扫描:上面的语句改成下面这样的,就可以使用索引了。

  • select * from dept shere staff_num < 1000 or dept_id > 1000;
1.6.2 使用 is null 或 is not null

        使用 is null 或is nuo null也会限制索引的使用,因为数据库并没有定义null值。如果被索引的列中有很多null,就不会使用这个索引,在sql语句中使用null会造成很多麻烦。解决这个问题的办法就是:建表时把需要索引的列定义为非空(not null)

1.6.3.使用函数

如果没有使用基于函数的索引,那么where子句中对存在索引的列使用函数时,会使优化器忽略掉这些索引。下面的查询就不会使用索引:

  • select * from staff where trunc(birthdate) = '01-MAY-82';

但是把函数应用在条件上,索引是可以生效的,把上面的语句改成下面的语句,就可以通过索引进行查找。

  • select * from staff where birthdate < (to_date('01-MAY-82') + 0.9999);
1.6.4.比较不匹配的数据类型

        比较不匹配的数据类型也是难于发现的性能问题之一。下面的例子中,dept_id是一个varchar2型的字段,在这个字段上有索引,但是下面的语句会执行全表扫描。

  • select * from dept where dept_id = 900198;

        这是因为oracle会自动把where子句转换成to_number(dept_id)=900198,就是3所说的情况,这样就限制了索引的使用。把SQL语句改为如下形式就可以使用索引

  • select * from dept where dept_id = '900198';
1.6.5.使用like子句

        使用like子句查询时,数据需要把所有的记录都遍历来进行判断,索引不能发挥作用,这种情况也要尽量避免。

  • Like 的字符串中第一个字符如果是‘%’则用不到索引
  • Column1 like ‘aaa%’ 是可以的
  • Column1 like ‘%aaa%’用不到
1.6.6.使用in

        尽管In写法要比exists简单一些,exists一般来说性能要比In要高的多,用In还是用Exists的时机,当in的集合比较小的时候,或者用Exists无法用到选择性高的索引的时候,用In要好,否则就要用Exists

1.6.7.如果能不用到排序,则尽量避免排序

        用到排序的情况有集合操作。Union ,minus ,intersect等,注:union all 是不排序的。Order by、Group by、Distinct、In 有时候也会用到排序,确实要排序的时候也尽量要排序小数据量,尽量让排序在内存中执行,

1.6.8 组合索引

如果使用的是组合索引,但是在过滤的时候没有使用前置索引列则也会失效。

1.6.9 使用<>、not in 、not exist,

对于这三种情况大多数情况下认为结果集很大,一般大于5%-15%就不走索引而走FTS。

1.7 全局索引

分区索引分为本地(local index)索引和全局索引(global index)

其中本地索引又可以分为有前缀(prefix)的索引和无前缀(nonprefix)的索引。而全局索引目前只支持有前缀的索引。B树索引和位图索引都可以分区,但是HASH索引不可以被分区。位图索引必须是本地索引。

一、本地索引特点:

1.    本地索引一定是分区索引,分区键等同于表的分区键,分区数等同于表的分区数,一句话,本地索引的分区机制和表的分区机制一样。

2.    如果本地索引的索引列以分区列开头,则称为前缀局部索引。

3.    如果本地索引的列不是以分区列开头,或者不包含分区列,则称为非前缀索引。

4.    前缀和非前缀索引都可以支持索引分区消除,前提是查询的条件中包含索引分区键。

5.    本地索引只支持分区内的唯一性,无法支持表上的唯一性,因此如果要用本地索引去给表做唯一性约束,则约束中必须要包括分区键列。

6.    本地分区索引是对单个分区的,每个分区索引只指向一个表分区,全局索引则不然,一个分区索引能指向n个表分区,同时,一个表分区,也可能指向n个索引分区,对分区表中的某个分区做truncate或者move,shrink等,可能会影响到n个全局索引分区,正因为这点,本地分区索引具有更高的可用性。

7.    位图索引只能为本地分区索引。

8.    本地索引多应用于数据仓库环境中。

本地索引:创建了一个分区表后,如果需要在表上面创建索引,并且索引的分区机制和表的分区机制一样,那么这样的索引就叫做本地分区索引。本地索引是由ORACLE自动管理的,它分为有前缀的本地索引和无前缀的本地索引。什么叫有前缀的本地索引?有前缀的本地索引就是包含了分区键,并且将其作为引导列的索引。什么叫无前缀的本地索引?无前缀的本地索引就是没有将分区键的前导列作为索引的前导列的索引。下面举例说明: 

create table test (id number,data varchar2(100))
partition by RANGE (id)
(
partition p1 values less than (1000) tablespace p1,
partition p2 values less than (2000) tablespace p2,
partition p3 values less than (maxvalue) tablespace p3
);

create index i_id on test(id) local; 因为id是分区键,所以这样就创建了一个有前缀的本地索引。

SQL> select dbms_metadata.get_ddl('INDEX','I_ID','ROBINSON') index_name FROM DUAL;------去掉了一些无用信息

INDEX_NAME

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

CREATE INDEX "ROBINSON"."I_ID" ON "ROBINSON"."TEST" ("ID") LOCAL

(PARTITION "P1" TABLESPACE "P1" ,PARTITION "P2" TABLESPACE "P2" ,PARTITION "P3" TABLESPACE "P3" );

也可以这样创建:

SQL> drop index i_id;

Index dropped

SQL> CREATE INDEX "ROBINSON"."I_ID" ON "ROBINSON"."TEST" ("ID") LOCAL
2 (PARTITION "P1" TABLESPACE "P1" , PARTITION "P2" TABLESPACE "P2" ,PARTITION "P3" TABLESPACE "P3" );

Index created

create index i_data on test(data) local;因为data不是分区键,所以这样就创建了一个无前缀的本地索引。

SQL> select dbms_metadata.get_ddl('INDEX','I_DATA','ROBINSON')index_name FROM DUAL;-

INDEX_NAME
--------------------------------------------------------------------------------

CREATE INDEX "ROBINSON"."I_DATA" ON "ROBINSON"."TEST" ("DATA")LOCAL
(PARTITION "P1" TABLESPACE "P1" ,PARTITION "P2" TABLESPACE "P2" ,PARTITION "P3" TABLESPACE "P3" );

从user_part_indexes视图也可以证明刚才创建的索引,一个是有前缀的,一个是无前缀的

SQL> select index_name,table_name,partitioning_type,locality,ALIGNMENT from user_part_indexes;

INDEX_NAME  TABLE_NAME  PARTITIONING_TYPE  LOCALITY  ALIGNMENT
------------------------------ ------------------------------ ----------------- -------- ------------
I_DATA                 TEST                   RANGE                    LOCAL     NON_PREFIXED
I_ID                      TEST                   RANGE                     LOCAL     PREFIXED

二、全局索引特点:

1.全局索引的分区键和分区数和表的分区键和分区数可能都不相同,表和全局索引的分区机制不一样。

2.全局索引可以分区,也可以是不分区索引,全局索引必须是前缀索引,即全局索引的索引列必须是以索引分区键作为其前几列。

3.全局分区索引的索引条目可能指向若干个分区,因此,对于全局分区索引,即使只截断一个分区中的数据,都需要rebulid若干个分区甚至是整个索引。

4.全局索引多应用于oltp系统中。

5.全局分区索引只按范围或者散列hash分区.

6.对分区表做move或者truncate的时可以用update global indexes语句来同步更新全局分区索引,用消耗一定资源来换取高度的可用性。

7.表用a列作分区,索引用b做局部分区索引,若where条件中用b来查询,那么oracle会扫描所有的表和索引的分区,成本会比分区更高,此时可以考虑用b做全局分区索引

全局索引:与本地分区索引不同的是,全局分区索引的分区机制与表的分区机制不一样。全局分区索引只能是B树索引,oracle只支持有前缀的全局索引。另外oracle不会自动的维护全局分区索引,当我们在对表的分区做修改之后,如果执行修改的语句不加上update global indexes的话,那么索引将不可用。

SQL> drop index i_id ;

Index dropped

SQL> create index i_id_global on test(id) global
Index created

SQL> alter table test drop partition p3;

Table altered

ORACLE默认不会自动维护全局分区索引,注意看status列,

SQL> select INDEX_NAME,PARTITION_NAME,STATUS from user_ind_partitions where index_name='I_ID_GLOBAL';

INDEX_NAME   PARTITION_NAME    STATUS
------------------------------ ------------------------------ --------
I_ID_GLOBAL           P1                     USABLE
I_ID_GLOBAL           P2                     USABLE

SQL> create index i_id_global on test(data) global
2 partition by range(id)
3 ( partition p1 values less than (2000) tablespace p1,
4 partition p2 values less than (maxvalue) tablespace p2
5 );

ORA-14038: GLOBAL 分区索引必须加上前缀

SQL> create bitmap index i_id_global on test(id) global
2 partition by range(id)
3 ( partition p1 values less than (2000) tablespace p1,
4 partition p2 values less than (maxvalue) tablespace p2
5 );

ORA-25113: GLOBAL 可能无法与位图索引一起使用

三、分区索引不能够将其作为整体重建,必须对每个分区重建

SQL> alter index i_id_global rebuild online nologging;

ORA-14086: 不能将分区索引作为整体重建

这个时候可以查询dba_ind_partitions,或者user_ind_partitions,找到partition_name,然后对每个分区重建

SQL> select index_name,partition_name from user_ind_partitions where index_name='I_ID_GLOBAL';

INDEX_NAME PARTITION_NAME
I_ID_GLOBAL P1
I_ID_GLOBAL P2

SQL> alter index i_id_global rebuild partition p1 online nologging;

Index altered

SQL> alter index i_id_global rebuild partition p2 online nologging;

Index altered

四、关于分区索引的几个视图

dba_ind_partitions 描述了每个分区索引的分区情况,以及统计信息
dba_part_indexes 分区索引的概要统计信息,可以得知每个表上有哪些分区索引,分区索引的类型(local/global)
dba_indexes minus dba_part_indexes (minus操作)可以得到每个表上有哪些非分区索引

常见的索引相关问题和解决方案
 

1.在查询数据的时候发现没有使用到索引。
             在很多时候系统的优化器认为全表扫描的效率要高于使用索引扫描,或者是在查询的时候查询条件中使用了一些函数等,来阻止了索引的使用,则直接不会使用索引。  
解决方案:
       查看执行计划,预期使用的索引是否被使用,如果没有使用,则需要检查是什么原因导致的。
       例如:
       --查询入职日期为2023年的员工的信息
        --原来方案:导致索引失效:原因:使用了函数
        SELECT * FROM emp WHERE to_char(hiredate,'yyyy')=2023;
        --创建一个索引
        CREATE INDEX hiredate_ind ON emp(hiredate);
        --优化之后的方案:索引生效
        SELECT * FROM emp WHERE hiredate BETWEEN to_date('2023-1-1','yyyy-mm-dd') AND to_date('2023-12-31','yyyy-mm-dd');
2.索引碎片:
       如果经常对表进行插入,删除,修改操作,就会导致索引碎片化,这样就会影响查询的效率。
解决方案:定期进行索引的重建,来减少索引碎片,但是需要注意就是,索引重建会消耗大量的资源,并且在重建期间索引不能被使用,所以需要选择一个比较合适的时间进行重建。可以先删除,然后再新建或者直接重建索引;重建索引的语法:alter index 索引名 rebuild;
3.索引过多
     如果表中由太多的索引,则可能会影响插入和修改的性能
     解决方案:定期要对索进行审查,删除一些不再需要的索引。
     删除索引的语法:drop index 索引名    

04-29 16:47