索引是应用设计和开发的一个重要方面。node
若是有太多的索引,DML的性能就会受到影响。sql
若是索引太少,又会影响查询(包括插入、更新和删除)的性能。数据库
Oracle提供了多种不一样类型的索引以供使用:缓存
B*树索引:B*树的构造相似于二叉树,能根据键提供一行或一个行集的快速访问,一般只需不多的读操做就能找到正确的行。”B*树“中的”B“不表明二叉(binary),而表明平衡(balanced)。B*树索引有如下子类型:网络
索引组织表(index organized table):索引组织表以B*树结构存储。堆表的数据行是以一种无组织的方式存储的(只要有可用的空间,就能够放数据),而 IOT中的数据要按主键的顺序存储和排序。对应用来讲,IOT表现得与“常规“表并没有二致;须要使用SQL来正确地访问IOT。IOT对信息获取、空间系统和OLAP应用最为有用。并发
B*树聚簇索引(B*tree cluster index)这些是传统B*树索引的一个变体(只是稍有变化)。B*树聚簇索引用于对聚簇键创建索引。在传统B*树中,键都指向一行;而B*树聚簇不一样,一个聚簇键会指向一个块,其中包含与这个聚簇键相关的多行。app
降序索引(descending index):降序索引容许数据在索引结构中按“从大到小“的顺序(降序)排序,而不是按”从小到大“的顺序(升序)排序。dom
反向键索引(reverse key index):这也是B*树索引,只不过键中的字节会“反转“。利用反向键索引,若是索引中填充的是递增的值,索引条目在索引中能够获得更均匀的分布。例如,若是使用一个序列来生成主键,这个序列将生成诸如987500、98750一、987502等值。这些值是顺序的,因此假若使用一个传统的B*树索引,这些值就可能放在同一个右侧块上,这就加重了对这一块的竞争。利用反向键,Oracle则会逻辑地对20578九、10578九、005789等创建索引。Oracle将数据放在索引中以前,将先把所存储数据的字节反转,这样原来可能在索引中相邻放置的值在字节反转以后就会相距很远。经过反转字节,对索引的插入就会分布到多个块上。ide
位图索引(bitmap index):在一颗B*树中,一般索引条目和行之间存在一种一对一的关系:一个索引条目就指向一行。而对于位图索引,一个索引条目则使用一个位图同时指向多行。位图索引适用于高度重复并且一般只读的数据(高度重复是指相对于表中的总行数,数据只有不多的几个不一样值)。考虑在一个有100万行的表中,每一个列只有3个可取值:Y、N和NULL。举例来讲,若是你须要频繁地统计多少行有值Y,这就很适合创建位图索引。不过并非说若是这个表中某一列有11.000个不一样的值就不能创建位图索引,这一列固然也能够创建位图索引。在一个OLTP数据库中,因为存在并发性相关的问题,因此不能考虑使用位图索引。注意,位图索引要求使用Oracle企业版或我的版。函数
位图联结索引(bitmap join index):这为索引结构(而不是表)中的数据提供了一种逆规范化的方法。例如,请考虑简单的EMP和DEPT表。多少人在位于波士顿的部门工做?EMP有一个指向DEPT的外键,要想统计LOC值为Boston的部门中的员工人数,一般必须完成表联结,将LOC列联结至EMP记录来回答这个问题。经过使用位图联结索引,则能够在EMP表上对LOC列创建索引。
基于函数的索引(function-based index):这些就是B*树索引或位图索引,它将一个函数计算获得的结果存储在行的列中,而不是存储列数据自己。能够把基于函数的索引看做一个虚拟列(或派生列)上的索引,换句话说,这个列并不物理存储在表中。基于函数的索引能够用于加快形如SELECT * FROM T WHERE FUNCTION(DATABASE_COLUMN) = SAME_VALUE这样的查询,由于值FUNCTION(DATABASE_COLUMN)已经提早计算并存储在索引中。
应用域索引(application domain index):应用域索引是你本身构建和存储的索引,可能存储在Oracle中,也可能在Oracle以外。你要告诉优化器索引的选择性如何,以及执行的开销有多大,优化器则会根据你提供的信息来决定是否使用你的索引。Oracle文本索引就是应用域索引的一个例子;你也可使用构建Oracle文本索引所用的工具来创建本身的索引。须要指出,这里建立的“索引“不须要使用传统的索引结构。例如,Oracle文本索引就使用了一组表来实现其索引概念。
--------------------------------------------------------------------------------------------------------
B*树索引
这是数据库中最经常使用的一类索引结构。其实现与二叉查找树很类似。其目标是尽量减小Oracle查找数据所花费的时间。
这个树最底层的块称为叶子节点(leaf node)或叶子块(leaf block),其中分别包含各个索引键以及一个rowid(指向所索引的行)。叶子节点之上的内部块称为分支块(branch block)。这些节点用于在结构中实现导航。例如,若是想在索引中找到值42,要从树顶开始,找到左分支。咱们要检查这个块,并发现须要找到范围在“42..50“的块。这个块将是叶子块,其中会指示包含数42的行。索引的叶子节点实际上构成了一个双向链表。一旦发现要从叶子节点中的哪里”开始“(也就是说,一旦发现第一个值),执行值的有序扫描(也称为索引区间扫描(index range scan))就会很容易。咱们不用再在索引结构中导航;而只需根据须要经过叶子节点向前或向后扫描就能够了。因此要知足诸如如下的谓词条件将至关简单:
where x between 20 and 30
Oracle发现第一个最小键值大于或等于20的索引叶子块,而后水平地遍历叶子节点链表,直到最后命中一个大于30的值。
B*树索引中不存在非唯一(nonunique)条目。在一个非唯一索引中,Oracle会把rowid做为一个额外的列(有一个长度字节)追加到键上,使得键唯一。例如,若是有一个CREATE INDEX I ON T(X,Y)索引,从概念上讲,它就是CREATE UNIQUE INDEX I ON T(X,Y,ROWID)。在一个唯一索引中,根据定义的唯一性,Oracle不会再向索引键增长rowid。在非唯一索引中,数据会首先按索引键值排序(依索引键的顺序)。而后按rowid升序排序。而在唯一索引中,数据只按索引键排序。
B*树的特色之一是:全部叶子块都应该在树的同一层上。这一层也称为索引的高度(height),这说明全部从索引的根块到叶子块的遍历都会访问一样数目的块。也就是说,对于形如”SELECT INDEXED_COL FROM T WHERE INDEXED_COL = :X“的索引,要到达叶子块来获取第一行,不论使用的:X值是什么,都会执行一样数目的I/O。换句话说,索引是高度平衡的(height balanced)。大多数B*树索引的高度都是2或者3,即便索引中有数百万行记录也是如此。这说明,通常来说,在索引中找到一个键只须要执行2或3次I/O。
Oracle在表示从索引根块到叶子块遍历所涉及的块数时用了两个含义稍有不一样的术语。第一个是高度(HEIGHT),这是指从根块到叶子块遍历所需的块数。使用ANALYZE INDEX <name> VALIDATE STRUCTURE命令分析索引后,能够从INDEX_STATS视图找到这个高度(HEIGHT)值。另外一个术语是BLEVEL,这是指分支层数,与HEIGHT相差1(BLEVEL不把叶子块层算在内)。收集统计信息后,能够在诸USER_INDEXES之类的常规字典表中找到BLEVEL值。
例如,假设有一个11,000,000行的表,其主键索引创建在一个数字列上:
scott@ORCL>select index_name, blevel, num_rows 2 from user_indexes 3 where table_name = 'BIG_TABLE'; INDEX_NAME BLEVEL NUM_ROWS ------------------------------ ---------- ---------- BIG_TABLE_PK 1 100000
BLEVEL为1,这说明HEIGHT为2,要找到叶子须要1个I/O(访问叶子自己还须要第2个I/O)。因此,要从这个索引中获取任何给定的键值,共须要2个I/O:
scott@ORCL>select id from big_table where id = 42; ID ---------- 42 执行计划 ---------------------------------------------------------- Plan hash value: 1688966252 -------------------------------------------------------------------------------- -- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | -------------------------------------------------------------------------------- -- | 0 | SELECT STATEMENT | | 1 | 5 | 1 (0)| 00:00:01 | |* 1 | INDEX UNIQUE SCAN| BIG_TABLE_PK | 1 | 5 | 1 (0)| 00:00:01 | -------------------------------------------------------------------------------- -- Predicate Information (identified by operation id): --------------------------------------------------- 1 - access("ID"=42) 统计信息 ---------------------------------------------------------- 0 recursive calls 0 db block gets 2 consistent gets 0 physical reads 0 redo size 521 bytes sent via SQL*Net to client 520 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 1 rows processed scott@ORCL>select id from big_table where id = 12345; ID ---------- 12345 执行计划 ---------------------------------------------------------- Plan hash value: 1688966252 -------------------------------------------------------------------------------- -- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | -------------------------------------------------------------------------------- -- | 0 | SELECT STATEMENT | | 1 | 5 | 1 (0)| 00:00:01 | |* 1 | INDEX UNIQUE SCAN| BIG_TABLE_PK | 1 | 5 | 1 (0)| 00:00:01 | -------------------------------------------------------------------------------- -- Predicate Information (identified by operation id): --------------------------------------------------- 1 - access("ID"=12345) 统计信息 ---------------------------------------------------------- 1 recursive calls 0 db block gets 2 consistent gets 1 physical reads 0 redo size 523 bytes sent via SQL*Net to client 520 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 1 rows processed scott@ORCL>select id from big_table where id = 1234567; 未选定行 执行计划 ---------------------------------------------------------- Plan hash value: 1688966252 -------------------------------------------------------------------------------- -- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | -------------------------------------------------------------------------------- -- | 0 | SELECT STATEMENT | | 1 | 5 | 1 (0)| 00:00:01 | |* 1 | INDEX UNIQUE SCAN| BIG_TABLE_PK | 1 | 5 | 1 (0)| 00:00:01 | -------------------------------------------------------------------------------- -- Predicate Information (identified by operation id): --------------------------------------------------- 1 - access("ID"=1234567) 统计信息 ---------------------------------------------------------- 1 recursive calls 0 db block gets 2 consistent gets 1 physical reads 0 redo size 331 bytes sent via SQL*Net to client 509 bytes received via SQL*Net from client 1 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 0 rows processed
B*树是一个绝佳的通用索引机制,不管是大表仍是小表都很适用,随着底层表大小的增加,获取数据的性能只会稍有恶化(或者根本不会恶化)。
对于B*树索引,从串联(多列)索引去除冗余。
压缩键索引(compressed key index)的基本概念是,每一个键条目分解为两个部分:“前缀”和“后缀”。前缀创建在串联索引(concatenated index)的前几列上,这些列有许多重复的值。后缀则在索引键的后几列上,这是前缀所在索引条目中的唯一部分(即有区别的部分)。
下面经过一个例子来讲明,建立一个表和一个串联索引,并使用ANALYZE INDEX测量无压缩时所用的空间。而后利用索引压缩建立这个索引,分别压缩不一样数目的键条目,查看有什么差异。下面先来看这个表和索引:
sys@ORCL>create table t 2 as 3 select * from all_objects; 表已建立。 sys@ORCL>create index t_idx on 2 t(owner,object_type,object_name); 索引已建立。 sys@ORCL>analyze index t_idx validate structure; 索引已分析
而后建立一个INX_STATS表,在这里存放INDEX_STATS信息,咱们把表中的行标记为“未压缩”(noncompressed):
sys@ORCL>create table idx_stats 2 as 3 select 'noncompressed' what, a.* 4 from index_stats a; 表已建立。
如今能够看到,OWNER部分重复了屡次,这说明,这个索引中的一个索引块可能有数十个条目,
能够从中抽取出重复的OWNER列
全部者(owner)名在叶子块上只出现了一次,而不是在每一个重复的条目上都出现一次。
运行如下脚本,传入数字 1 做为参数来从新建立这个索引,在此索引使用了第一列的压缩:
drop index t_idx; create index t_idx on t(owner,object_type,object_name) compress &1; analyze index t_idx validate structure; insert into idx_stats select 'compress &1', a.* from index_stats a;
为了进行比较,咱们不只在压缩一列的基础上运行了这个脚本,还分别使用了两个和3个压缩列来运行这个脚本,查看会发生什么状况。最终,咱们将查询IDX_STATS,应该能观察到如下信息:
sys@ORCL>select what, height, lf_blks, br_blks, 2 btree_space, opt_cmpr_count, opt_cmpr_pctsave 3 from idx_stats 4 / WHAT HEIGHT LF_BLKS BR_BLKS BTREE_SPACE OPT_CMPR_COUNT OPT_CM PR_PCTSAVE ------------- ------ ------- ------- ----------- -------------- ---------------- noncompressed 3 503 3 4048096 2 28 compress 1 3 446 3 3590312 2 19 compress 2 3 359 3 2894660 2 0 compress 3 3 562 4 4525880 2 35
能够看到,COMPRESS 1索引的大小大约是无压缩索引的89%(经过比较BTREE_SPACE得出)。叶子块数大幅度降低。更进一步,使用COMPRESS 2时,节省的幅度更为显著。所获得的索引大约是原索引(无压缩索引)的72%.实际上,利用列OPT_CMPR_PCTSAVE的信息(这表明最优的节省压缩百分比(optimum compression percent saved)或指望从压缩获得的节省幅度)。咱们能够猜想出COMPRESS 2索引的大小:
sys@ORCL>select 4048096*(0.28) from dual; 4048096*(0.28) -------------- 1133466.88 sys@ORCL>select 4048096-2894660 from dual; 4048096-2894660 --------------- 1153436
对无压缩索引执行ANALYZE命令时,会填写OPT_CMPR_PCTSAVE/OPT_CMPR_COUNT列,并估计出:利用COMPRESS 2,能够节省28%的空间;
不过,再看看COMPRESS 3。若是压缩3列,所获得的索引实际上会更大:是原来索引大小的110%。这是由于:每删除一个重复的前缀,能节省N个副本的空间,可是做为压缩机制的一部分,这会在叶子块上增长4字节的开销。把OBJECT_NAME列增长到压缩键后,则使得这个键是唯一的;在这种状况下,则说明没有重复的副本能够提取。所以,最后的结果就是:咱们只是向每一个索引键条目增长了4个字节,而不能提取出任何重复的数据。IDX_STATS中的OPT_CMPR_COUNT列真是精准无比,确实给出了可用的最佳压缩数,OPT_COMPR_PCTSAVE则指出了能够获得多大的节省幅度。
对如今来讲,这种压缩不是免费的。如今压缩索引比原来更复杂了。Oracle会花更多的时间来处理这个索引结构中的数据,不光在修改期间维护索引更耗时,查询期间搜索索引也更花时间。利用压缩,块缓冲区缓存比之前能存放更多的索引条目,缓冲命中率可能会上升,物理I/O应该降低,可是要多占用一些CPU时间来处理索引,还会增长块竞争的可能性。对于散列聚簇,获取100万个随机的行可能占用更多的CPU时间,可是I/O数会减半;这里也是同样,咱们必须清楚存在的这种折中。若是你如今已经在大量占用CPU时间,在增长压缩键索引只能拔苗助长,这会减慢处理的速度。另外一方面,若是目前的I/O操做不少,使用压缩键索引就能加快处理速度。
B*树索引的另外一个特色是可以将索引键“反转”。 实现B*树索引的目的是为了减小“右侧”索引中对索引叶子块的竞争,好比在一个Oracle RAC环境中,某些列用一个序列值或时间戳填充,这些列上创建的索引就属于“右侧”(right-hand-side)索引。
RAC是一种Oracle配置,其中多个实例能够装载和打开同一个数据库。若是两个实例须要同时修改同一个数据块,它们会经过一个硬件互连(interconnect)来回传递这个块来实现共享,互连是两个(或多个)机器之间的一条专用网络链接。若是某个利用一个序列填充,这个列上有一个主键索引,那么每一个人插入新值时,都会视图修改目前索引结构右侧的左块(索引中较高的值都放在右侧,而较低的值放在左侧)。若是对用序列填充的列上的索引进行修改,就会汇集在不多的一组叶子块上。假若将索引的键反转,对索引进行插入时,就能在索引中的全部叶子键上分布开(不过这每每会使索引不能获得充分地填充)。
反向键能够用做一种减小竞争的方法(即便只有一个Oracle实例)。反向键主要用于缓解忙索引右侧的缓冲区忙等待。
在介绍如何度量反向键索引的影响以前,咱们先来讨论物理上反向键索引会作什么。反向键索引只是将索引键中各个列的字节反转。若是考虑9010一、90102和90103这样几个数,使用Oracle DUMP函数查看其内部表示,能够看到这几个数的表示以下:
scott@ORCL>select 90101, dump(90101,11.) from dual 2 union all 3 select 90102, dump(90102,11.) from dual 4 union all 5 select 90103, dump(90103,11.) from dual 6 / 90101 DUMP(90101,11.) ---------- ----------------------- 90101 Typ=2 Len=4: 195,10,2,2 90102 Typ=2 Len=4: 195,10,2,3 90103 Typ=2 Len=4: 195,10,2,4
每一个数的长度都是4字节,它们只是最后一个字节有所不一样。这些数最后可能在一个索引结构中向右依次放置。不过,若是反转这些数的字节,Oracle就会插入如下值:
scott@ORCL>select 90101, dump(reverse('90101'),11.) from dual 2 union all 3 select 90102, dump(reverse('90102'),11.) from dual 4 union all 5 select 90103, dump(reverse('90103'),11.) from dual 6 / 90101 DUMP(REVERSE('90101'),11.) ---------- ---------------------------- 90101 Typ=96 Len=5: 49,48,49,48,57 90102 Typ=96 Len=5: 50,48,49,48,57 90103 Typ=96 Len=5: 51,48,49,48,57
这些数彼此之间最后会“相距很远“。这样访问同一个块(最右边的块)的RAC实例个数就能减小,相应地,在RAC实例之间传输的块数也会减小。反向键索引的缺点之一是:能用常规索引的地方不必定能用反向键索引。例如,在回答如下谓词时,X上的反向键索引就没有:
where x > 5
存储以前,数据不是按X在索引中排序,而是按REVERSE(X)排序,所以,对X>5的区间扫描不能使用这个索引。另外一方面,有些区间扫描确实能够在反向键索引上完成。若是在(X,Y)上有一个串联索引,如下谓词就可以利用反向键索引,并对它执行“区间扫描“:
where x = 5
这是由于,首先将X的字节反转,而后再将Y的字节反转。Oracle并非将(X||Y)的字节反转,而是会存储(REVERSE(X) || REVERSE(Y))。这说明, X = 5的全部值会存储在一块儿,因此Oracle能够对这个索引执行区间扫描来找到全部这些数据。
下面,假设在一个用序列填充的表上有一个代理主键(surrogate primary key),并且不须要在这个(主键)索引上使用区间扫描,也就是说,不须要作MAX(primary_key)、MIN(primary_key)、WHERE primary_key < 100等查询,在有大量插入操做的状况下,即便只有一个Oracle实例,也能够考虑使用反向键索引。我创建了两个不一样的测试,一个是在纯PL/SQL环境中进行测试,另外一个使用了Pro*C,我想经过这两个测试来展现反向键索引和传统索引对插入的不一样影响,即若是一个表的主键上有一个反向键索引,与有一个传统索引的状况相比,完成插入时会有什么差异。在这两种状况下,所用的表都是用如下DDL建立的(这里使用了ASSM来避免表块的竞争,这样能够把索引块的竞争隔离开):
scott@ORCL>create table t tablespace assm 2 as 3 select 0 id, a.* 4 from all_objects a 5 where 1<0; 表已建立。 scott@ORCL>alter table t 2 add constraint t_pk 3 primary key (id) 4 using index (create index t_pk on t(id) REVERSE tablespace assm); 表已更改。 scott@ORCL>create sequence s cache 1000; 序列已建立。 scott@ORCL>create or replace procedure do_sql 2 as 3 begin 4 for x in ( select rownum r, all_objects.* from all_objects ) 5 loop 6 insert into t 7 ( id, OWNER, OBJECT_NAME, SUBOBJECT_NAME, 8 OBJECT_ID, DATA_OBJECT_ID, OBJECT_TYPE, CREATED, 9 LAST_DDL_TIME, TIMESTAMP, STATUS, TEMPORARY, 10 GENERATED, SECONDARY ) 11 values 12 ( s.nextval, x.OWNER, x.OBJECT_NAME, x.SUBOBJECT_NAME, 13 x.OBJECT_ID, x.DATA_OBJECT_ID, x.OBJECT_TYPE, x.CREATED, 14 x.LAST_DDL_TIME, x.TIMESTAMP, x.STATUS, x.TEMPORARY, 15 x.GENERATED, x.SECONDARY ); 16 if ( mod(x.r,100) = 0 ) 17 then 18 commit; 19 end if; 20 end loop; 21 commit; 22 end; 23 / 过程已建立。
使用了Pro*C来模拟一个数据仓库抽取、转换和加载(extract, transform, load, ETL)例程,它会在提交之间一次成批地处理100行(即每次提交前都处理100行):
exec sql declare c cursor for select * from all_objects; exec sql open c; exec sql whenever notfound do break; for(;;) { exec sql fetch c into :owner:owner_i, :object_name:object_name_i, :subobject_name:subobject_name_i, :object_id:object_id_i, :data_object_id:data_object_id_i, :object_type:object_type_i, :created:created_i, :last_ddl_time:last_ddl_time_i, :timestamp:timestamp_i, :status:status_i, :temporary:temporary_i, :generated:generated_i, :secondary:secondary_i; exec sql insert into t ( id, OWNER, OBJECT_NAME, SUBOBJECT_NAME, OBJECT_ID, DATA_OBJECT_ID, OBJECT_TYPE, CREATED, LAST_DDL_TIME, TIMESTAMP, STATUS, TEMPORARY, GENERATED, SECONDARY ) values ( s.nextval, :owner:owner_i, :object_name:object_name_i, :subobject_name:subobject_name_i, :object_id:object_id_i, :data_object_id:data_object_id_i, :object_type:object_type_i, :created:created_i, :last_ddl_time:last_ddl_time_i, :timestamp:timestamp_i, :status:status_i, :temporary:temporary_i, :generated:generated_i, :secondary:secondary_i ); if ( (++cnt%100) == 0 ) { exec sql commit; } } exec sql whenever notfound continue; exec sql commit; exec sql close c;
它容许在索引中以降序(从大到小的顺序)存储一列,而不是升序(从小到大)存储。在以前的Oracle版本(即Oracle8i之前的版本)中,尽管语法上也支持DESC(降序)关键字,可是通常都会将其忽略,这个关键字对于索引中数据如何存储或使用没有任何影响。不过,在Oracle8i及以上版本中,DESC关键字确实会改变建立和使用索引的方式。
例如,若是使用先前的表T,并以下查询这个表:
scott@ORCL>set autotrace traceonly explain scott@ORCL>select owner, object_type 2 from t 3 where owner between 'T' and 'Z' 4 and object_type is not null 5 order by owner DESC, object_type DESC; 执行计划 ---------------------------------------------------------- Plan hash value: 961378228 --------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | --------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 28 | 3 (34)| 00:00:01 | | 1 | SORT ORDER BY | | 1 | 28 | 3 (34)| 00:00:01 | |* 2 | TABLE ACCESS FULL| T | 1 | 28 | 2 (0)| 00:00:01 | --------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - filter("OBJECT_TYPE" IS NOT NULL AND "OWNER">='T' AND "OWNER"<='Z') scott@ORCL>create index desc_t_idx on t(owner desc,object_type asc); 索引已建立。 scott@ORCL>exec dbms_stats.gather_index_stats( user, 'DESC_T_IDX' ); PL/SQL 过程已成功完成。 scott@ORCL>select owner, object_type 2 from t 3 where owner between 'T' and 'Z' 4 and object_type is not null 5 order by owner DESC, object_type ASC; 执行计划 ---------------------------------------------------------- Plan hash value: 2494308350 ------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 28 | 0 (0)| 00:00:01 | |* 1 | INDEX RANGE SCAN| DESC_T_IDX | 1 | 28 | 0 (0)| 00:00:01 | ------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 1 - access(SYS_OP_DESCEND("OWNER")>=HEXTORAW('A5FF') AND SYS_OP_DESCEND("OWNER")<=HEXTORAW('ABFF') ) filter("OBJECT_TYPE" IS NOT NULL AND SYS_OP_UNDESCEND(SYS_OP_DESCEND("OWNER"))>='T' AND SYS_OP_UNDESCEND(SYS_OP_DESCEND("OWNER"))<='Z')
仅当要经过索引访问表中不多的一部分行(只占一个很小的百分比)时,才使用B*树在列上创建索引。
若是要处理表中的多行,并且可使用索引而不用表,就可使用一个B*树索引。
根据以上建议,有两种使用索引的方法:
索引用于访问表中的行:经过读索引来访问表中的行。此时你但愿访问表中不多的一部分行(只占一个很小的百分比)。
索引用于回答一个查询:索引包含了足够的信息来回答整个查询,我根本不用去访问表。在这种状况下,索引则用做一个“较瘦“版本的表。
第一种状况(也就是说,为了访问表中不多的一部分行而使用索引)是指,若是有一个表T(仍是使用前面的表T),并有以下的一个查询计划:
scott@ORCL>set autotrace traceonly explain scott@ORCL>select owner, status 2 from t 3 where owner = USER; 执行计划 ---------------------------------------------------------- Plan hash value: 1049179052 -------------------------------------------------------------------------------- ---------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | -------------------------------------------------------------------------------- ---------- | 0 | SELECT STATEMENT | | 1 | 22 | 0 (0)| 00:00:01 | | 1 | TABLE ACCESS BY INDEX ROWID| T | 1 | 22 | 0 (0)| 00:00:01 | |* 2 | INDEX RANGE SCAN | DESC_T_IDX | 1 | | 0 (0)| 00:00:01 | -------------------------------------------------------------------------------- ---------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - access(SYS_OP_DESCEND("OWNER")=SYS_OP_DESCEND(USER@!)) filter(SYS_OP_UNDESCEND(SYS_OP_DESCEND("OWNER"))=USER@!)
那你就应该只访问这个表的不多一部分行(只占一个很小的百分比)。这里要注意TABLE ACCESS BY INDEX ROWID后面的INDEX (RANGE SCAN)。这说明,Oracle会读索引,而后会对索引条目执行一个数据库块读(逻辑或物理I/O)来获得行数据。若是你要经过索引访问T中的大量行(占很大的百分比),这就不是最高效的方法了。
在第二种状况下(也就是说,能够用索引而没必要用表),你能够经过索引处理表中100%的行(或者实际上能够是任何比例)。使用索引能够只是为了建立一个“较瘦“版本的表。如下查询演示了这个概念:
scott@ORCL>select count(*) 2 from t 3 where owner = user; 执行计划 ---------------------------------------------------------- Plan hash value: 2221593640 -------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | -------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 17 | 0 (0)| 00:00:01 | | 1 | SORT AGGREGATE | | 1 | 17 | | | |* 2 | INDEX RANGE SCAN| DESC_T_IDX | 1 | 17 | 0 (0)| 00:00:01 | -------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - access(SYS_OP_DESCEND("OWNER")=SYS_OP_DESCEND(USER@!)) filter(SYS_OP_UNDESCEND(SYS_OP_DESCEND("OWNER"))=USER@!)
在此,只使用了索引来回答查询,如今访问多少行都不要紧,由于咱们只会使用索引。从查询计划能够看出,这里从未访问底层表;咱们只是扫描了索引结构自己。
若是必须完成TABLE ACCESS BY INDEX ROWID,就必须确保只访问表中不多的一部分块(只占很小的百分比),这一般对应为不多的一部分行,或者须要可以尽量快地获取前面的几行。若是咱们访问的行太多(所占百分比过大,不在总行数的1%~20%之间),那么与全表扫描相比,经过B*树索引来访问这些数据一般要花更长的时间。
对于第二种类型的查询,即答案彻底能够在索引中找到,状况就彻底不一样了。咱们会读一个索引块,选出许多“行“来处理,而后再到另外一个索引块,如此继续,在此从不访问表。某些状况下,还能够在索引上执行一个快速全面扫描,从而更快地回答这类查询。快速全面扫描是指,数据库不按特定的顺序读取索引块,而只是开始 读取它们。这里再也不是把索引只用做一个索引,此时索引更像是一个表。若是采用快速全面扫描,将再也不按索引条目的顺序来获得行。
通常来说,B*树索引会放在频繁使用查询谓词的列上,并且咱们但愿从表中只返回少许的数据(只占很小的百分比),或者最终用户请求当即获得反馈。在一个瘦(thin)表(也就是说,只有不多的几个列,或者列很小)上,这个百分比可能至关小。使用这个索引的查询应该只获取表中2%~3%(或者更少)的行。在一个胖(fat)表中(也就是说,这个表有不少列,或者列很宽),百分比则可能会上升到表的20%~25%。以上建议不必定直接适用于每个人;这个比例并不直观,但很精确。索引按索引键的顺序存储。索引会按键的有序顺序进行访问。索引指向的块则随机地存储在堆中。所以,咱们经过索引访问表时,会执行大量分散、随机的I/O。这里“分散“(scattered)是指,索引会告诉咱们读取块1,而后是块11.000、块20五、块32一、块一、块11.03二、块1,等等,它不会要求咱们按一种连续的方式读取块一、而后是块2,接着是块3.咱们将以一种很是随意的方式读取和从新读取块。这种块I/O可能很是慢。
假设有一个表,其中的行主键由一个序列来填充。向这个表增长数据时,序列号相邻的行通常存储位置也会彼此“相邻“。
表会很天然地按主键顺序聚簇(由于数据或多或少就是已这种属性增长的)。固然,它不必定严格按照键聚簇(要想作到这一点,必须使用一个IOT),可是,通常来说,主键值彼此接近的行的物理位置也会“靠“在一块儿。若是发出如下查询:
select * from T where primary_key between :x and :y
你想要的行一般就位于一样的块上。在这种状况下,即便要访问大量的行(占很大的百分比),索引区间扫描可能也颇有用。缘由在于:咱们须要读取和从新读取的数据库块极可能会被缓存,由于数据共同放置在同一个位置(co-located)。另外一方面,若是行并不是共同存储在一个位置上,使用这个索引对性能来说可能就是灾难性的。
只需一个小小的演示就能说明这一点。首先建立一个表,这个表主要按其主键排序:
system@ORCL>create table colocated ( x int, y varchar2(80) ); 表已建立。 system@ORCL>begin 2 for i in 1 .. 100000 3 loop 4 insert into colocated(x,y) 5 values (i, rpad(dbms_random.random,75,'*') ); 6 end loop; 7 end; 8 / PL/SQL 过程已成功完成。 system@ORCL>alter table colocated 2 add constraint colocated_pk 3 primary key(x); 表已更改。 system@ORCL>begin 2 dbms_stats.gather_table_stats( user, 'COLOCATED', cascade=>true ); 3 end; 4 / PL/SQL 过程已成功完成。
这个表正好知足前面的描述,即在块大小为8KB的一个数据库中,每块有大约100行。在这个表中,,X = 11.2,3的行极有可能在同一个块上。仍取这个表,但有意地使它“无组织“。在COLOCATED表中,咱们建立一个Y列,它带有一个前导随机数,如今利用这一点使得数据”无组织“,即再也不按主键排序:
system@ORCL>create table disorganized 2 as 3 select x,y 4 from colocated 5 order by y; 表已建立。 system@ORCL>alter table disorganized 2 add constraint disorganized_pk 3 primary key (x); 表已更改。 system@ORCL>begin 2 dbms_stats.gather_table_stats( user, 'DISORGANIZED', cascade=>true ); 3 end; 4 / PL/SQL 过程已成功完成。
能够证实,这两个表是同样的——这是一个关系数据库,因此物理组织对于返回的答案没有影响(至少关于数据库理论的课程中就是这么教的)。实际上,尽管会返回相同的答案,但这两个表的性能特征却有着天壤之别。给定一样的问题,使用一样的查询计划,查看TKPROF(SQL跟踪)输出,能够看到如下报告:
CPU Elapsd Physical Rds Executions Rds per Exec %Total Time (s) Time (s) Hash Value --------------- ------------ -------------- ------ -------- --------- ---------- 1,063 3 354.3 78.3 0.28 0.66 2088343704 select * from colocated where x between 20000 and 40000 942 1 942.0 69.4 0.31 1.27 3151995455 select /*+ index( disorganized disorganized_pk ) */* from disorg anized where x between 20000 and 40000 ... CPU Elapsd Old Buffer Gets Executions Gets per Exec %Total Time (s) Time (s) Hash Value --------------- ------------ -------------- ------ -------- --------- ---------- 21,374 1 21,374.0 65.1 0.31 1.27 3151995455 select /*+ index( disorganized disorganized_pk ) */* from disorg anized where x between 20000 and 40000 6,673 3 2,224.3 20.3 0.28 0.66 2088343704 select * from colocated where x between 20000 and 40000
在个人数据库中(块大小为8KB),这些表的总块数以下:
system@ORCL>select table_name, blocks 2 from user_tables 3 where table_name in ( 'COLOCATED', 'DISORGANIZED' ); TABLE_NAME BLOCKS ------------------------------ ---------- COLOCATED 1191 DISORGANIZED 1191
每一个逻辑I/O都涉及缓冲区缓存的一个或多个锁存器。在一个多用户/CPU状况下,在咱们自旋并等待锁存器时,与第一个查询相比,第二个查询所用的CPU时间无疑会高出几倍。
ARRAYSIZE对逻辑I/O的影响
ARRAYSIZE是客户请求下一行时Oracle向客户返回的行数。客户将缓存这些行,在向数据库请求下一个行集以前会先使用缓存的这些行,ARRAYSIZE对查询执行的逻辑I/O可能有很是重要的影响,这是由于,若是必须跨数据库调用反复地访问同一个块(也就是说,经过多个数据库调用反复访问同一个块,这里特别是指跨获取调用),Oracle就必须一而再、再而三地从缓冲区缓存获取这个块。
聚簇因子
USER_INDEXES视图中的CLUSTERING_FACTOR列
根据索引的值指示表中行的有序程度:
若是这个值与块数接近,则说明表至关有序,获得了很好的组织,在这种状况下,同一个叶子块中的索引条目可能指向同一个数据块上的行。
若是这个值与行数接近,表的次序可能就是很是随机的。在这种状况下,同一个叶子块上的索引条目不太可能指向同一个数据块上的行。
能够把聚簇因子(clustering factor)看做是经过索引读取整个表时对表执行的逻辑I/O次数。也就是说,CLUSTERING_FACTOR指示了表相对于索引自己的有序程度,查看这些索引时,会看到如下结果:
system@ORCL>select a.index_name, 2 b.num_rows, 3 b.blocks, 4 a.clustering_factor 5 from user_indexes a, user_tables b 6 where index_name in ('COLOCATED_PK', 'DISORGANIZED_PK' ) 7 and a.table_name = b.table_name 8 / INDEX_NAME NUM_ROWS BLOCKS CLUSTERING_FACTOR ------------------------------ ---------- ---------- ----------------- COLOCATED_PK 100000 1191 1190 DISORGANIZED_PK 100000 1191 99915
因此数据库说:“若是经过索引COLOCATED_PK从头至尾地读取COLOCATED表中的每一行,就要执行1190次I/O。不过,若是咱们对DISORGANIZED表作一样的事情,则会对这个表执行99,915次I/O。“之因此存在这么大的区别,缘由在于,当Oracle对索引结构执行区间扫描时,若是它发现索引中的下一行与前一行在同一个数据库块上,就不会再执行另外一个I/O从缓冲区缓存中得到表块。它已经有表块的一个句柄,只需直接使用就能够了。不过,若是下一行不在同一个块上,就会释放当前的这个块,而执行另外一个I/O从缓冲区缓存获取要处理的下一个块。所以,在咱们对索引执行区间扫描时,COLOCATED_PK索引会发现下一行几乎总于前一行在同一个块上。DISORGANIZED_PK索引起现的状况则刚好相反。经过使用提示,让优化器使用索引全面扫描来读取整个表,再统计非NULL的Y值个数,就能看到经过索引读取整个表须要执行多少次I/O:
select count(Y) from (select /*+ INDEX(COLOCATED COLOCATED_PK) */ * from colocated) CPU CPU per Elapsd Old Time (s) Executions Exec (s) %Total Time (s) Buffer Gets Hash Value ---------- ------------ ---------- ------ ---------- --------------- ---------- 0.11 1 0.11 24.1 0.39 1,399 2513878408 Elapsed Elap per CPU Old Time (s) Executions Exec (s) %Total Time (s) Physical Reads Hash Value ---------- ------------ ---------- ------ ---------- --------------- ---------- 0.39 1 0.39 17.8 0.11 1,399 2513878408 CPU Elapsd Old Buffer Gets Executions Gets per Exec %Total Time (s) Time (s) Hash Value --------------- ------------ -------------- ------ -------- --------- ---------- 1,399 1 1,399.0 15.3 0.11 0.39 2513878408 CPU Elapsd Physical Rds Executions Rds per Exec %Total Time (s) Time (s) Hash Value --------------- ------------ -------------- ------ -------- --------- ---------- 1,399 1 1,399.0 52.5 0.11 0.39 2513878408 CPU per Elap per Old Executions Rows Processed Rows per Exec Exec (s) Exec (s) Hash Value ------------ --------------- ---------------- ----------- ---------- ---------- 1 1 1.0 0.11 0.39 2513878408 % Total Old Parse Calls Executions Parses Hash Value ------------ ------------ -------- ---------- 1 1 1.56 2513878408
select count(Y) from (select /*+ INDEX(DISORGANIZED DISORGANIZED_PK) */ * from disorganized) CPU CPU per Elapsd Old Time (s) Executions Exec (s) %Total Time (s) Buffer Gets Hash Value ---------- ------------ ---------- ------ ---------- --------------- ---------- 0.31 1 0.31 52.6 1.29 100,175 204991286 Elapsed Elap per CPU Old Time (s) Executions Exec (s) %Total Time (s) Physical Reads Hash Value ---------- ------------ ---------- ------ ---------- --------------- ---------- 1.29 1 1.29 70.2 0.31 1,403 204991286 CPU Elapsd Old Buffer Gets Executions Gets per Exec %Total Time (s) Time (s) Hash Value --------------- ------------ -------------- ------ -------- --------- ---------- 100,175 1 100,175.0 95.5 0.31 1.29 204991286 CPU Elapsd Physical Rds Executions Rds per Exec %Total Time (s) Time (s) Hash Value --------------- ------------ -------------- ------ -------- --------- ---------- 1,403 1 1,403.0 98.4 0.31 1.29 204991286 CPU per Elap per Old Executions Rows Processed Rows per Exec Exec (s) Exec (s) Hash Value ------------ --------------- ---------------- ----------- ---------- ---------- 1 1 1.0 0.31 1.29 204991286 % Total Old Parse Calls Executions Parses Hash Value ------------ ------------ -------- ---------- 1 1 1.25 204991286
system@ORCL>SET AUTOTRACE ON system@ORCL>select count(Y) from 2 (select /*+ INDEX(COLOCATED COLOCATED_PK) */ * from colocated) 3 ; COUNT(Y) ---------- 100000 执行计划 ---------------------------------------------------------- Plan hash value: 3483305348 ------------------------------------------------------------------------------ --------------- | Id | Operation | Name | Rows | Bytes | Cost (%C PU)| Time | ------------------------------------------------------------------------------ --------------- | 0 | SELECT STATEMENT | | 1 | 76 | 1402 (1)| 00:00:17 | | 1 | SORT AGGREGATE | | 1 | 76 | | | | 2 | TABLE ACCESS BY INDEX ROWID| COLOCATED | 100K| 7421K| 1402 (1)| 00:00:17 | | 3 | INDEX FULL SCAN | COLOCATED_PK | 100K| | 211 (1)| 00:00:03 | ------------------------------------------------------------------------------ --------------- 统计信息 ---------------------------------------------------------- 1 recursive calls 0 db block gets 1399 consistent gets 1399 physical reads 0 redo size 527 bytes sent via SQL*Net to client 519 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 1 rows processed system@ORCL>select count(Y) from 2 (select /*+ INDEX(DISORGANIZED DISORGANIZED_PK) */ * from disorganized) ; COUNT(Y) ---------- 100000 执行计划 ---------------------------------------------------------- Plan hash value: 102678025 ------------------------------------------------------------------------------ ------------------ | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------ ------------------ | 0 | SELECT STATEMENT | | 1 | 76 | 100 K (1)| 00:20:03 | | 1 | SORT AGGREGATE | | 1 | 76 | | | | 2 | TABLE ACCESS BY INDEX ROWID| DISORGANIZED | 100K| 7421K| 100 K (1)| 00:20:03 | | 3 | INDEX FULL SCAN | DISORGANIZED_PK | 100K| | 211 (1)| 00:00:03 | ------------------------------------------------------------------------------ ------------------ 统计信息 ---------------------------------------------------------- 1 recursive calls 0 db block gets 100124 consistent gets 0 physical reads 0 redo size 527 bytes sent via SQL*Net to client 519 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 1 rows processed
tkprof 报告以下:
SQL ID: db146dyxc9ctw Plan Hash: 3483305348 select count(Y) from (select /*+ INDEX(COLOCATED COLOCATED_PK) */ * from colocated) call count cpu elapsed disk query current rows ------- ------ -------- ---------- ---------- ---------- ---------- ---------- Parse 1 0.00 0.00 0 0 0 0 Execute 1 0.00 0.00 0 0 0 0 Fetch 2 0.03 0.03 0 1399 0 1 ------- ------ -------- ---------- ---------- ---------- ---------- ---------- total 4 0.03 0.03 0 1399 0 1 Misses in library cache during parse: 0 Optimizer mode: ALL_ROWS Parsing user id: 5 Rows Row Source Operation ------- --------------------------------------------------- 1 SORT AGGREGATE (cr=1399 pr=0 pw=0 time=0 us) 100000 TABLE ACCESS BY INDEX ROWID COLOCATED (cr=1399 pr=0 pw=0 time=40664 us cost=1402 size=7600000 card=100000) 100000 INDEX FULL SCAN COLOCATED_PK (cr=209 pr=0 pw=0 time=13426 us cost=211 size=0 card=100000)(object id 81368)
******************************************************************************** select count(Y) from (select /*+ INDEX(DISORGANIZED DISORGANIZED_PK) */ * from disorganized) call count cpu elapsed disk query current rows ------- ------ -------- ---------- ---------- ---------- ---------- ---------- Parse 1 0.00 0.00 0 0 0 0 Execute 1 0.00 0.00 0 0 0 0 Fetch 2 0.12 0.12 0 100124 0 1 ------- ------ -------- ---------- ---------- ---------- ---------- ---------- total 4 0.12 0.12 0 100124 0 1 Misses in library cache during parse: 1 Optimizer mode: ALL_ROWS Parsing user id: 5 Rows Row Source Operation ------- --------------------------------------------------- 1 SORT AGGREGATE (cr=100124 pr=0 pw=0 time=0 us) 100000 TABLE ACCESS BY INDEX ROWID DISORGANIZED (cr=100124 pr=0 pw=0 time=140919 us cost=100177 size=7600000 card=100000) 100000 INDEX FULL SCAN DISORGANIZED_PK (cr=209 pr=0 pw=0 time=17519 us cost=211 size=0 card=100000)(object id 81370)
在这两种状况下,索引都须要执行209次逻辑I/O(Row Source Operation行中的cr-209)。若是
将一致读(consistent read)总次数减去209,只测量对表执行的I/O次数,就会发现所获得的数字与各个索引的聚簇因子相等。COLOCATED_PK是“有序表“的一个经典例子,DISORGANIZE_PK则是一个典型的”表次序至关随机“的例子。如今来看看这对优化器有什么影响。若是咱们想获取25,000行,Oracle对两个索引都会选择全表扫描(经过索引获取25%的行不是最优计划,即便是对颇有序的表也是如此)。不过,若是只选择表数据的11%,就会观察到如下结果:
select * from colocated where x between 20000 and 30000 call count cpu elapsed disk query current rows ------- ------ -------- ---------- ---------- ---------- ---------- ---------- Parse 1 0.00 0.01 0 0 0 0 Execute 1 0.00 0.00 0 0 0 0 Fetch 668 0.00 0.02 0 1452 0 10001 ------- ------ -------- ---------- ---------- ---------- ---------- ---------- total 670 0.00 0.03 0 1452 0 10001 Misses in library cache during parse: 1 Optimizer mode: ALL_ROWS Parsing user id: 5 Rows Row Source Operation ------- --------------------------------------------------- 10001 TABLE ACCESS BY INDEX ROWID COLOCATED (cr=1452 pr=0 pw=0 time=27435 us cost=142 size=810162 card=10002) 10001 INDEX RANGE SCAN COLOCATED_PK (cr=689 pr=0 pw=0 time=13544 us cost=22 size=0 card=10002)(object id 81368) ******************************************************************************** select * from disorganized where x between 20000 and 30000 call count cpu elapsed disk query current rows ------- ------ -------- ---------- ---------- ---------- ---------- ---------- Parse 1 0.00 0.00 0 0 0 0 Execute 1 0.00 0.00 0 0 0 0 Fetch 668 0.03 0.06 1 1855 0 10001 ------- ------ -------- ---------- ---------- ---------- ---------- ---------- total 670 0.03 0.06 1 1855 0 10001 Misses in library cache during parse: 1 Optimizer mode: ALL_ROWS Parsing user id: 5 Rows Row Source Operation ------- --------------------------------------------------- 10001 TABLE ACCESS FULL DISORGANIZED (cr=1855 pr=1 pw=0 time=25769 us cost=326 size=810162 card=10002)
这里的表结构和索引与前面彻底同样,但聚簇因子有所不一样。在这种状况下,优化器为COLOCATED表选择了一个索引访问计划,而对DISORGANIZED表选择了一个全面扫描访问计划。要记住,11%并非一个阀值,它只是一个小于25%的数,并且在这里会致使对COLOCATED表的一个索引区间扫描。
以上讨论的关键点是,索引并不必定老是合适的访问方法。优化器也许选择不使用索引,并且如前面的例子所示,这种选择可能很正确。影响优化器是否使用索引的因素有不少,包括物理数据布局。所以,你可能会矫枉过正,力图重建全部的表来使全部索引有一个好的聚簇因子,可是在大多数状况下这可能只会浪费时间。只有当 你在对表中的大量数据(所占百分比很大)执行索引区间扫描时,这才会产生影响。另外必须记住,对于一个表来讲,通常只有一个索引能有合适的聚簇因子!表中 的行可能只以一种方式排序。在前面所示的例子中,若是Y列上还有一个索引,这个索引在COLOCATED表中可能就不能很好地聚簇,而在DISORGANIZED表中则刚好相反。若是你认为数据物理聚簇很重要,能够考虑使用一个IOT、B*树聚簇,或者在连续地重建表时考虑散列聚簇。
B*树小结 B*树索引是到目前为止Oracle数据库中最经常使用的索引结构。它们是绝好的通用索引机制。在访问时间方面提供了很大的可扩缩性,从一个11行的索引返回数据所用的时间与一个100,000行的索引结构中返回数据的时间是同样的。 何时创建索引,在哪些列上创建索引,你的设计中必须注意这些问题。索引并不必定就意味着更快的访问;实际上你会发现,在许多状况下,若是Oracle使 用索引,反而会使性能降低。这实际上两个因素的一个函数,其中一个因素是经过索引须要访问表中多少数据(占多大的百分比),另外一个因素是数据如何布局。若是能彻底使用索引“回答问题“(而不用表),那么访问大量的行(占很大的百分比)就是有意义的,由于这样能够避免读表所带来的额外的分散I/O。若是使用索引来访问表,可能就要确保只处理整个表中的不多一部分(只占很小的百分比)。 应该在应用的设计期间考虑索引的设计和实现,而不要过后才想起来。若是对如何访问数据作了精心的计划和考虑,大多数状况下就能清楚地知道须要什么索引。