1.为什么需要index
为访问表数据提供另一种选择
当从表中读取数据时,Oracle有两个方法:
从表中读取每一行(全表扫描)
通过ROWID一次读取一行
【
扩展ROWID格式为
OOOOOO FFF BBBBBB RRR
数据对象号 相对文件号 块号 行号
】
当我们要访问大型表的少数行时,可能需要使用索引。
因为如果没有索引,那么只能进行全表扫描。
2.index的类型,创建,维护
index是独立的段,可与和它相关的表位于不同的表空间中
2.1 B树索引
在叶子节点,存放键值和表中对应这个键值的rowid
适用于唯一键值或键值很少重复的情况。
对于那些键值大量重复的列,B-Tree索引不适用。
(1)标准索引(非唯一索引)
创建
CREATE INDEX item_index ON item(itemcode) TABLESPACE index_tbs;
查询
select * from user_indexes;
select * from user_ind_columns;
select * from dba_indexes;
select * from dba_ind_columns;
重建索引
ALTER INDEX item_index REBUILD;
删除索引
DROP INDEX item_index;
alter index indname monitoring usage; 打开索引监控
v$object_usage 查看索引是否被使用
alter index indname nomonitoring usage; 关闭索引监控
(2)唯一索引
确保在定义索引的列中没有重复值
Oracle 自动在表的主键列上创建唯一索引[primary key]
CREATE UNIQUE INDEX item_index ON item(itemcode) TABLESPACE index_tbs;
(3)组合索引
当某个索引包含有多个列时,称这个索引为组合索引。
如果 SQL 语句的 WHERE 子句中引用了组合索引的所有列或大多数列,则可以提高检索速度
在使用组合索引的时候,要谨慎选择索引列中的列顺序。
一般来说,索引的第一列应该是最有可能在where子句中使用的列,并且也是在索引中最具有选择性的列。
CREATE INDEX comp_index ON item(p_category, itemrate) TABLESPACE index_tbs;
select * from item where p_category='...' and itemrate=...;
(4)基于函数的索引
基于一个或多个列上的函数或表达式创建的索引
创建时必须具有 QUERY REWRITE 权限 老师说不一定需要这个权限 (查询重写)
CREATE INDEX lowercase_idx ON toys( LOWER(toyname) ) TABLESPACE index_tbs;
SELECT * FROM toys WHERE LOWER(toyname)='doll';
索引段中存储的数据为函数运算后的索引
(5)反向键索引
反转索引列键值的每个字节
通常建立在值是连续增长的列上,使数据均匀地分布在整个索引上
CREATE INDEX rev_index ON item (itemcode) REVERSE;
把反向键索引转成标准索引
ALTER INDEX rev_index REBUID NOREVERSE;
2.2 位图索引
适合创建在低基数列上,位图索引不直接存储ROWID,而是存储字节位到ROWID的映射,对索引列的每一个键值分别索引。
索引的存储:索引的键值 当前范围的起始rowid和终止rowid 这个键值在这个范围内的位置编码
将这个十六进制编码转化为二进制后,编码值是1的代表记录符合索引的键值,是0则表示不符合。
只记录当前范围的起始rowid和终止rowid,不会记录全部rowid
键值的每个存储范围的大小和分段依据,主要与每次DML操作影响的记录数和这些键值相同的记录
在数据库中的物理分布有关。二者比较起来,前者的影响要大得多。用insert来举例。
如果插入一条记录,那么oracle会为这条记录中的键值建立一个范围,范围的大小是8条记录
(bitmap索引中的最小范围,所有bitmap索引的范围必是8的倍数,其他7条记录为空,可以为后续的插入操作继续使用)
如果执行一条批量插入语句(insert select),对于这条插入语句中包含的每一个键值,都会建立一个范围。
范围的大小为CEIL(操作记录数/8)*8。实际情况由于考虑利用原有范围内的空闲空间,以及考虑到键值的
rowid分布等因素,要远比这里描述的复杂得多,而且对于delete update操作而言,情况更为复杂。但有一点
是明确的,即Oracle会对批量插入(修改)的数据一起进行索引。如果一次性插入1000条键值相同的数据,则只会索引
一次,并生成一个包括1000条记录的范围。如果一条一条地插入1000条数据,则会索引1000次,并生成125个包括
8条记录的范围。无论是从效率的角度还是空间的角度考虑,批量操作都是对包含bitmap索引的表的首选。
适用于数据仓库
检查是否支持bitmap
select * from v$option where parameter like 'Bit%';
Bit-mapped indexes TRUE
show parameter area_size
bitmap_merge_area_size bitmap index进行merge操作时使用的区域
create_bitmap_area_size 创建bitmap索引的内存区域
Bitmap index 的特点 【可存储空值】
(1)比B-Tree索引节省空间
(2)建立速度较快(B-Tree需要排序,bitmap index不需要排序)
(3)基于规则的优化器无法使用bitmap索引
[] oracle 10g 已经不支持只有基于成本的优化器才能使用bitmap索引 []oracle 8就出现了
(4)bitmap index可以存储空值,
B-Tree索引不存储空值,因此对空值进行查询的,可以使用bitmap index ,l
分别看下面2个SQL的执行计划
select * from emp where empno=7369
select * from emp where empno is null
(5)Bitmap index 更适合计算记录总数,即select count(*)
create table test_bitmap as select * from dba_objects;
create bitmap index ind_b_owner on test_bitmap(owner);
analyze table test_bitmap compute statistics;
看执行计划:
select count(*) from test_bitmap;
select owner,count(*) from test_bitmap group by owner;
select * from test_bitmap where wner='SCOTT' (COST=103)
select * from test where wner='SCOTT' (cost=45)
(6)bitmap index 对批量DML操作只需要索引一次
(7)bitmap index的锁机制
相同键值的记录处在同一个索引范围中。
对一个范围中的某一行记录加锁(delete update),会引起对这个范围内的所有记录的加锁。
建立了bitmap index 的表,锁的最小粒度是bitmap索引的范围。这意味着对于多用户并行访问时,
表被锁定的几率大大增加。因此,对bitmap索引操作时,应采用批量修改、迅速提交的方式。
(8)bitmap索引适用包含键值数量有限,每个键值重复记录较多的列。
对于采用批量DML操作或无DML操作的列,效率很高。
更适用于数据仓库,OLTP中bitmap index并不是首选。
对于分区表,bitmap index只能采用local,不能使用global
创建bitmap index
CREATE BITMAP INDEX bit_index ON order_master (orderno) TABLESPACE index_tbs;
2.3 HASH索引
应用在HASH聚簇
HASH键+集群键+数据
通过HASH函数得出的值找到HASH键
使用HASH索引必须要使用HASH集群
建立HASH集群的同时,也就定义了一个HASH键
2.4 局部索引和全局索引
应用在分区表中
2.5 bitmap join index
bitmap join index不是建立在一张表上,是建立在2个或多个表的链接上。
可以在查询时消除对表的链接操作,而通过访问索引得到相应的结果。
通过MV也可实现这种功能。但使用bitmap join index比MV节省大量空间。
alter table test_bitmap add (id number);
update test_bitmap set id=rownum;
alter table test_bitmap add constraint pk_testbitmap primary key (id);
create table test_bitmap_join(id number,f_id number,comments varchar2(200));
nsert into test_bitmap_join select rownum,mod(id,1000)+1,null from test_bitmap;
commit;
alter table test_bitmap_join add constraint pk_tbj_f_id foreign key (f_id) references test_bitmap(id);
建立bitmap join index要求相链接的2张表存在主从关系。
看执行计划:
select a.* from test_bitmap_join a, test_bitmap bwhere a.f_id=b.id and b.owner='SCOTT';
在没有建立bitmap join index时,Oracle必须把2个表join(nested loops),cost=6714
创建bitmap join index
create bitmap index ind_b_test_bitmap_owner on test_bitmap_join (test_bitmap.owner)
from test_bitmap_join,test_bitmap where test_bitmap_join.f_id=test_bitmap.id;
查看索引信息:
select * from user_indexes
select * from user_ind_columns
看执行计划
select a.* from test_bitmap_join a, test_bitmap b
where a.f_id=b.id and b.owner='SCOTT'
cost=17
bitmap join index有4种模型:
F代表事实表
D代表维度表
pk维度表的主键列
fk事实表连接到维度表的外键列
(1)D c1 -- d.pk=f.fk --> F (c1 is a column of D)
create bitmap index bji on f(d.c1) from f,d where d.pk=f.fk;
上例中就是这种模型
where d.c1=...
and d.pk=f.fk
(2)D c1 c2 -- d.pk=f.fk --> F (c1 and c2 are columns of D)
create bitmap index bji on f(d.c1,d.c2) from f,d where d.pk=f.fk;
where d.c1=... and d.c2=...
and d.pk=f.fk
(3)D1 c1 -- d1.pk=f.fk1 --> F <--d2.pk=f.fk2-- D2 c2
create bitmap index bji on f(d1.c1,d2.c2) from f,d1,d2 where d1.pk=f.fk1 and d2.pk=f.fk2;
where d.c1=... and d2.c2=...
and d1.pk=f.fk1
and d2.pk=f.fk2
(4)D1 c3 -- d1.pk=d2.c2 --> D2 c2 --d2.pk=f.fk--> F
create bitmap index bji on f(d1.c3) from f,d1,d2 where d1.pk=d2.c2 and d2.pk=f.fk;
where d1.c3=...
and d1.pk=d2.c2
and d2.pk=f.fk
bitmap join index 的一些约束:
(1)只有事实表支持并行DML操作,维度表的并行DML操作将导致索引不可用;
(2)对于使用了bitmap join index的表,不同的事务只能同时更新其中的一张表;
(3)在表链接中,每个表只能出现一次;
(4)不能在IOT和临时表中建立bitmap join index;
(5)被索引的列必须是维度表的列;
(6)维度表参与连接的列必须是主键列或者具有唯一约束;
(7)如果维度表使用复合主键,则主键中的每一列都必须在连接中出现。
3.索引的性能考虑
索引改进性能的程度取决于三个因素:
1、数据的选择性 选择性 【数据唯一的程度】选择性高唯一性高
2、表数据在数据块上的分布 集群因子 clustering factor 【】
[
(1)加大块大小
(2)消除索引和表的碎片
(3)重建表,对其数据按索引排序
]
3、索引的高度 二元高度(binary height)
如果选择性很高(例如身份证号码),那么根据索引值返回的ROWID很少,
如果选择性很低(例如国家)则返回的ROWID很多,那么索引的性能将会大大降低
如果选择性很高,但是相关的行在表中的存储位置并不互相靠近,则会进一步减少索引的益处,
如果匹配索引值的数据分散在表的多个数据块中,则必须从表中选择多个单独的块以满足查询,
基于索引的读取是单块读取,如果使用全表扫描,使用的是多块读取以快速扫描表,
因此全表扫描不见得比索引扫描速度慢,但要看具体的块数。
选择性
user_indexes.distinct_keys
索引列中唯一值的个数
select t.index_name,t.table_name,
t.distinct_keys,t.num_rows,
t.clustering_factor,t.blevel
from dba_indexes t
where wner='SCOTT'
索引的选择性越高,针对每个不同值返回的行数也越少.可以帮助基于成本的优化器来判断执行路径.
索引只能单块读,全表扫描一次可以读很多块。
当集群因子高的话,当提取数据时索引只能一块一块的读数据,这样就比全表扫描慢了。
集群因子 clustering factor
B树叶块和表数据之间的关系称为clustering factor
Clustering Factor计算的方法如下:
1、扫描一个索引
2、比较某行的rowid和前一行的rowid,如果这两个rowid不属于同一个数据块,那么cluster factor增加1
3、整个索引扫描完毕后,就得到了该索引的cluster factor。
索引叶子块指向的表数据块越少,该值越小,在索引范围扫描的性能越好。
如果该值和表的块数接近,表示表行按索引顺序排序,
如果该值和表的行数接近,表示表行不是按索引顺序排序。
在计算索引访问成本的时候,这个值十分有用。
Clustering Factor乘以选择性参数(selectivity )就是访问索引的开销。
如果这个统计数据不能真实反映出索引的真实情况,
那么可能会造成优化器错误的选择执行计划。
所以,当出现较大数据删除或更新后,需要重建索引。
另外如果某张表上的大多数访问是按照某个索引做索引扫描,
那么将该表的数据按照索引字段的顺序重新组织,可以提高该表的访问性能。
聚集因子的数量和表的块数一致,因此表的数据排序和索引一致,
因此在进行索引范围扫描的时候,对表的访问成本还是比较低的。
聚集因子主要影响的是索引范围扫描。
如果聚集因子很高,那么建议对表进行重新整理,降低聚集因子。
集群因子记录在扫描索引时将读取的块数量。
集群因子是索引与它所基于的表相比较而得出的有序性度量,它用于检查在
索引访问之后执行的表查找的成本。(将集群因子与选择性相乘即可得到该操作的成本)。
如果使用的索引具有较大的集群因子,则必须访问更多的表数据块才可以获得每个
索引块中的行(因为邻近行位于不同的块中)。
如果集群因子接近于表中的块数量,则表示索引已适当排序;
如果集群因子接近于表中的行数量,则表示索引没有适当排序。
集群因子的计算简要介绍如下:
(1) 按顺序扫描索引。
(2) 将当前索引值指向的ROWID的块部分与以前的索引值进行比较(比较索引中的邻近行)。
(3) 如果ROWID指向不同的TABLE块,则增加集群因子(对整个索引执行该操作)。
Clustering_Factor列位于USER_INDEXES视图中,
该列反映了数据相对于已索引的列是否显得有序。
如果Clustering_Factor列的值接近于索引中的
树叶块(leaf block)的数目,表中的数据就是有序的。
索引的树叶块存储索引值以及它们指向的ROWID。
例如,CUSTOMERS表上Customer_Id列的值可以由
序列生成器产生,而且是表CUSTOMERS上的主键。
Customer_Id的索引的集群因子就有可能非常接近于
树叶块数(表示有序)。当往数据库中添加客户数据时,
它们就按照序列产生器所产生的序列值有序地存储在表中。
然而,因为整个表的客户名字排列是随机的,
所以customer_name上的索引会有一个很高的集群因子。
集群因子对执行范围扫描的SQL语句有一定的影响。
如果集群因子很低(相对于树叶块的数量),
需要读取的表中块的数量就可以减少很多。
这样也增加了相同的数据块已经存在于内存中的可能性。
一个较高的集群因子(相对于树叶块的数量)会增加满足基于索引列的范围查询所需的数据块数目。
二元高度(binary height)
dba_indexes.blevel
如果索引只占用了一个数据块,则二元高度是0
索引查找分为两个过程:
1、根据树进行定位、找出ROWID(索引查找)
2、根据ROWID找出表中的数据行(表数据查找)
进行索引查找的时候,首先从树根开始读数据,通过中间节点,
最后定位到叶节点,整个过程只能进行单数据块的读取。
索引的二元高度对把ROWID返回给用户进程时所要求的I/O数量起到关键作用。
二元高度的每个级别都会有一个读取块,而且由于这些块不能按顺序读取,
它们都要求一个独立的I/O操作。
检索一个二元高度为3的索引,返回一行数据给用户,同时有4个块被读取:3个来自索引,一个来自表。
IndexBlock O --> O --> O --> TableBlock O
随着索引的二元高度的增加,检索数据所要求的I/O数量也会随之增加。
在对索引进行分析后,可以通过查询DBA_INDEXES的blevel列查看它的二元高度.
一般来说,二元高度越低越好(最低为0)。作为数据库管理员,就是需要想方设法让这个
二元高度的值变为0。虽然这个目标看起来比较简单,但是实现起来却有相当大的困难。
在对索引进行优化的时候,就是要想尽一切办法,把这个值降低到最低。而要实现降低
这个二元高度的值的目标,则数据库管理员就必须先了解哪些因素跟这个值有关。
如此的话,数据库管理员才能够对症下药,降低二元高度的值,提高索引的性能。
1)、删除操作。有时候数据库管理员可能会发现他们刚完成数据的导入工作,
就会发现数据库的性能有所下降。而此时查询这个二元高度的值,则会发现这个值非常的大。
照理来说,才刚完成数据的导入功能,还没有进行其它的一些业务操作,这个值不应该很大呀。
那这是怎么一回事情呢?其实,这主要是因为大量的删除操作所造成的。原来在导入数据的时候,
可能会发现某些数据导入有问题。故有些数据库管理员会利用delete语句清除导入到数据库中的记录。
此时数据库管理员就需要注意,索引上如果有大量被删除的行,则它对应的二元高度的值也就会逐渐增加。
遇到这种情况时,数据库管理员可以尝试重建索引。通常情况下,如果二元高度的值比较大确实是因为
删除操作所引起的,那么通过这个重建索引的工作后,基本上可以把这个二元高度的值降下去。
笔者建议,如果一个索引中被删除掉记录接近于全部记录的30%左右,此时数据库管理员就需要
采取重建索引的作业,用于降低二元高度以及在一次I/O过程中所读取的空闲时间。
2)、数据块尺寸。通常情况下,数据库块尺寸与二元高度的值是成反比的。
数据块尺寸越大,则索引的二元高度就越低。
3)、二元高度的值还会随着索引列中的非NULL数量以及索引列中值的范围狭窄程度而变化。
一般来说,索引列中非NULL的数量越多(即空字段越少)则其二元高度的值越低。或者说,
索引列中的值越靠近,即范围比较小,则其二元高度的值也就越低。
为此,这就提醒数据库管理员在设计索引的时候,最好能够把索引字段设置为非空。
4.索引的使用
SELECT、UPDATE、DELETE+WHERE条件可以从索引中得到好处
(前提是:当访问的行数较少时)
一般来说,增加索引会带来insert语句性能的下降
如果根据未索引列update索引列,那么也会带来性能的降低
大量的delete也会因为索引的存在而导致性能降低
因此我们要分析具体的情况,判断索引和DML语句之间的关系
A 全表扫描(Full Table Scan FTS)
Oracle读取表中所有的行、多块读操作可以大大的减少IO的次数、
利用多块读可以大大的提高全表扫描的速度、只有在全表扫描的情况下才能使用多块读。
在较大的表上不建议使用全表扫描、如果读取表的数据总量超过5%—10%,那么通常进行全表扫描。
并行查询可能会使得我们的路径选择采用全表扫描。
db_file_multiblock_read_count
select * from emp
B 通过ROWID对表进行读取(Table access by ROWID)
ROWID指出了数据文件、块号、行号,通过ROWID是oracle数据库中读取单行数据最快速的方法。
这种方法不会采用多块读、而是会采用单块读的方式。
select t.*,t.rowid from emp t;
select * from emp where rowid='AAAMfMAAEAAAAAfAAA';
C 索引查找
通过索引找到数据行的ROWID、然后通过ROWID直接到表中查找数据,这种方式称为索引查找或者索引扫描。
因为一个ROWID对应一个数据行,因此这种方式采用的也是单块读。
select * from emp where empno=7788
访问路径走的是主键索引,因此是INDEX UNIQUE SCAN
首先是索引扫描、然后是根据索引查找到的ROWID进行表的访问。
根据索引的类型和where条件的限制不同,有四种索引扫描类型:
1)、索引唯一扫描(INDEX UNIQUE SCAN)
2)、索引范围扫描(INDEX RANGE SCAN)
3)、索引跳跃扫描 ()
4)、索引全扫描(INDEX FULL SCAN)有序
5)、索引快速扫描(INDEX FAST FULL SCAN) 无序
(1)索引唯一扫描
通过唯一键、主键,oracle通常返回一个数据行,因此采用的是索引唯一扫描
select * from emp where empno=7788
(2)索引范围扫描
如果索引不是唯一型索引,那么,通过此索引的访问将是索引范围扫描。
从根本上说,索引范围扫描和索引唯一扫描是一样的。
1、在唯一键上使用range操作符(>、<、<>、>=、<=、between)
2、在组合索引上,只使用部分列进行查询、导致查询出多行
3、对非唯一索引列上进行的查询
select * from emp where empno>7788
(3)索引跳跃扫描
在复合索引中,比如索引包含两个列:
A列、B列,如果以B列为条件进行查询,将使用索引跳跃扫描。
1 2
2 2
3 1
4 2
使用跳跃式索引的条件
1 优化器认为是合适的
2 索引中的前导列的唯一值的数量能满足一定的条件
3 优化器要知道前导列的值分布(通过分析/统计表得到)
4 合适的SQL语句
如果oracle没有选择使用跳跃式索引扫描,那么可能选择使用索引快速全局扫描或全表扫描。
(4)索引全扫描
查询出的数据必须全部从索引中得到
索引全扫描(Index Full Scan)不读取索引结构上的每个块,
它根据部分枝块,找到第一个叶块,然后按叶块双向链表的顺序,处理所有的叶块。
顺序读取,可以一次读取多个块.
create index emp_full on emp(empno,ename,deptno);
select empno,ename,deptno
from emp
where deptno=30;
Index Full Scan
查询的所有列都在索引里,
索引的前置列并不在where条件中
select empno,ename,deptno
from emp
where empno=7788; -- index range scan
select empno,ename,deptno
from emp ; --Index Full Scan
没有where子句,但是也可能使用到索引
查询索引列的MIN或者MAX
对索引列执行count
select max(empno) from emp
(5)索引快速全扫描
索引快速全扫描是把索引当作表一样进行全扫描操作
扫描索引块中的所有数据块,这点与index full scan相似,
但是索引快速扫描不进行数据的排序,在这种方式下,
可以使用多块读功能、也可以使用并行读功能,最大化数据的吞吐量。
db_file_multiblock_read_count有效
select ename,dept.deptno from emp,dept;
5. 有索引,但Oracle不使用索引的情况
1)、使用不等于运算符(<>、!=)
在where中使用不等于条件,将会使索引失效。
select * from emp where empno<>7788;
table access full
2)、使用 IS NULL或IS NOT NULL
在where子句中使用IS NULL或者IS NOT NULL同样会限制索引的使用。
如果被索引的列在某些行中存在NULL值,在索引列中就不会有相应的条目。
(例外:位图索引对于NULL列也会进行记录,因此位图索引对于NULL搜索通常较为快速)。
select * from emp where empno is null
因此建议对列加上DEFAULT 。
3)、使用函数
如果不使用基于函数的索引,那么在SQL语句的where子句中对存在索引的列使用函数时,会使优化器忽略掉这些索引。
一些常见的函数:trunc、substr、to_date、to_char、instr等,都可能会使索引失效。
解决方案:
1、使用基于函数的索引
2、灵活书写SQL、避免在索引列上使用SQL函数
4)数据类型不一致
select * from test where id=1
--id varchar2,是索引列
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/20670907/viewspace-742562/
,如需转载,请注明出处,否则将追究法律责任。