[转载]oracle索引类型及扫描方式大整理
oracle索引类型及扫描方式大整理
1.
索引在各种关系型数据库系统中都是举足轻重的组成部分,其对于提高检索数据的速度起至关重要的作用。在Oracle中,索引基本分为以下几种:B*Tree索引,反向索引,降序索引,位图索引,函数索引,interMedia全文索引等。本文主要就前6种索引进行分析。
首先给出各种索引的简要解释:
b*tree index:几乎所有的关系型数据库中都有b*tree类型索引,也是被最多使用的。其树结构与二叉树比较类似,根据rid快速定位所访问的行。
反向索引:反转了b*tree索引码中的字节,是索引条目分配更均匀,多用于并行服务器环境下,用于减少索引叶的竞争。
降序索引:8i中新出现的索引类型,针对逆向排序的查询。
位图索引:使用位图来管理与数据行的对应关系,多用于OLAP系统。
函数索引:这种索引中保存了数据列基于function返回的值,在select * from table where function(column)=value这种类型的语句中起作用。
B*Tree索引
B*Tree索引是最常见的索引结构,默认建立的索引就是这种类型的索引。B*Tree索引在检索高基数数据列(高基数数据列是指该列有很多不同的值)时提供了最好的性能。当取出的行数占总行数比例较小时B-Tree索引比全表检索提供了更有效的方法。但当检查的范围超过表的10%时就不能提高取回数据的性能。B-Tree索引是基于二叉树的,由分支块(branch block)和叶块(leaf block)组成。在树结构中,位于最底层底块被称为叶块,包含每个被索引列的值和行所对应的rowid。在叶节点的上面是分支块,用来导航结构,包含了索引列(关键字)范围和另一索引块的地址,如图26-1所示。
假设我们要找索引中值为80的行,从索引树的最上层入口开始,定位到大于等于50,然后往左找,找到第2个分支块,定位为75-100,最后再定位到叶块上,找到80所对应的rowid,然后根据rowid去读取数据块获取数据。如果查询条件是范围选择的,比如where column >20 and column <80,那么会先定位到第一个包含20的叶块,然后横向查找其他的叶块,直到找到包含80的块为止,不用每次都从入口进去再重新定位。
反向索引
反向索引是B*Tree索引的一个分支,它的设计是为了运用在某些特定的环境下的。Oracle推出它的主要目的就是为了降低在并行服务器(Oracle Parallel Server)环境下索引叶块的争用。当B*Tree索引中有一列是由递增的序列号产生的话,那么这些索引信息基本上分布在同一个叶块,当用户修改或访问相似的列时,索引块很容易产生争用。反向索引中的索引码将会被分布到各个索引块中,减少了争用。反向索引反转了索引码中每列的字节,通过dump()函数我们可以清楚得看见它做了什么。举个例子:1,2,3三个连续的数,用dump()函数看它们在Oracle内部的表示方法。
SQL> select 'number',dump(1,16) from dual
'NUMBE DUMP(1,16)
------ -----------------
number Typ=2 Len=2: c1,2 (1)
number Typ=2 Len=2: c1,3 (2)
number Typ=2 Len=2: c1,4 (3)
再对比一下反向以后的情况:
SQL> select 'number',dump(reverse(1),16) from
dual
'NUMBE DUMP(REVERSE(1),1
------ -----------------
number Typ=2 Len=2: 2,c1 (1)
number Typ=2 Len=2: 3,c1 (2)
number Typ=2 Len=2: 4,c1 (3)
我们发现索引码的结构整个颠倒过来了,这样1,2,3个索引码基本上不会出现在同一个叶块里,所以减少了争用。不过反向索引又一个缺点就是不能在所有使用常规索引的地方使用。在范围搜索中其不能被使用,例如,where column>value,因为在索引的叶块中索引码没有分类,所以不能通过搜索相邻叶块完成区域扫描。
函数索引
基于函数的索引也是8i以来的新产物,它有索引计算列的能力,它易于使用并且提供计算好的值,在不修改应用程序的逻辑上提高了查询性能。使用基于函数的索引有几个先决条件:
(1)必须拥有QUERY REWRITE(本模式下)或GLOBAL QUERY REWRITE(其他模式下)权限。
(2)必须使用基于成本的优化器,基于规则的优化器将被忽略。
(3)必须设置以下两个系统参数:
QUERY_REWRITE_ENABLED=TRUE
QUERY_REWRITE_INTEGRITY=TRUSTED
可以通过alter system set,alter session set在系统级或线程级设置,也可以通过在init.ora添加实现。
这里举一个基于函数的索引的例子:
SQL> create index test.ind_fun on test.testindex(upper(a));
索引已创建。
SQL> insert into testindex values('a',2);
已创建 1 行。
SQL> commit;
提交完成。
SQL> select *
A
-- ----------
a
Execution Plan
----------------------------------------------------------
(优化器选择了全表扫描)
--------------------------------------------------------------------
SQL> select *
A
-- ----------
a
Execution Plan
----------------------------------------------------------
降序索引
SQL> select * from test where a between 1 and 100 order by a desc,b asc;
Execution Plan
----------------------------------------------------------
SQL> create index test.ind_desc on test.testrev(a desc,b asc);
SQL> analyze index test.ind_desc compute statistics;
SQL> select * from test where a between 1 and 100 order by a desc,b asc;
Execution Plan(SQL执行计划,稍后会讲解如何使用)。
----------------------------------------------------------
1
值 1 2 3 4 5 6 7 8 9 10
Male 1 0 0 0 0 0 0 0 1 1
Female 0 1 1 1 0 0 1 1 0 0
Null 0 0 0 0 1 1 0 0 0 0
2.
这里介绍CBO根据统计数值得知进行全Oracle索引扫描比进行全表扫描更有效时,才进行全Oracle索引扫描,而且此时查询出的数据都必须从索引中可以直接得到。
学习Oracle时,你可能会遇到Oracle索引扫描问题,这里将介绍Oracle索引扫描问题的解决方法,在这里拿出来和大家分享一下。根据索引的类型与where限制条件的不同,有4种类型的Oracle索引扫描:
◆索引唯一扫描(index unique scan)
◆索引范围扫描(index range scan)
◆索引全扫描(index full scan)
◆索引快速扫描(index fast full scan)
(1) 索引唯一扫描(index unique scan)
通过唯一索引查找一个数值经常返回单个ROWID。如果该唯一索引有多个列组成(即组合索引),则至少要有组合索引的引导列参与到该查询中,如创建一个索引:create index idx_test on emp(ename, deptno, loc)。则select ename from emp where ename = ‘JACK’ and deptno = ‘DEV’语句可以使用该索引。如果该语句只返回一行,则存取方法称为索引唯一扫描。而select ename from emp where deptno = ‘DEV’语句则不会使用该索引,因为where子句种没有引导列。如果存在UNIQUE 或PRIMARY KEY 约束(它保证了语句只存取单行)的话,Oracle经常实现唯一性扫描。
使用唯一性约束的例子:
(2) 索引范围扫描(index range scan)
使用一个索引存取多行数据,同上面一样,如果索引是组合索引,如(1)所示,而且select ename from emp where ename = ‘JACK’ and deptno = ‘DEV’语句返回多行数据,虽然该语句还是使用该组合索引进行查询,可此时的存取方法称为索引范围扫描。在唯一索引上使用索引范围扫描的典型情况下是在谓词(where限制条件)中使用了范围操作符(如>、<、<>、>=、<=、between)
使用索引范围扫描的例子:
在非唯一索引上,谓词col = 5可能返回多行数据,所以在非唯一索引上都使用索引范围扫描。
使用index rang scan的3种情况:
(a) 在唯一索引列上使用了range操作符(> < <> >= <= between)
(b) 在组合索引上,只使用部分列进行查询,导致查询出多行
(c) 对非唯一索引列上进行的任何查询。
(3) 索引全扫描(index full scan)
与全表扫描对应,也有相应的全Oracle索引扫描。在某些情况下,可能进行全Oracle索引扫描而不是范围扫描,需要注意的是全Oracle索引扫描只在CBO模式下才有效。 CBO根据统计数值得知进行全Oracle索引扫描比进行全表扫描更有效时,才进行全Oracle索引扫描,而且此时查询出的数据都必须从索引中可以直接得到。
全Oracle索引扫描的例子:
(4) 索引快速扫描(index fast full scan)
扫描索引中的所有的数据块,与 index full scan很类似,但是一个显著的区别就是它不对查询出的数据进行排序,即数据不是以排序顺序被返回。在这种存取方法中,可以使用多块读功能,也可以使用并行读入,以便获得最大吞吐量与缩短执行时间。
索引快速扫描的例子:
index full scan和Index Fast Full Scan(全索引扫描和快速全索引扫描)
index full scan和index fast full
scan是指同样的东西吗?答案是no。两者虽然从字面上看起来差不多,但是实现的机制完全不同。我们一起来看看两者的区别在哪里?
首先来看一下IFS,FFS能用在哪里:在一句sql中,如果我们想搜索的列都包含在索引里面的话,那么index full scan 和
index fast full scan 都可以被采用代替full table scan。比如以下语句:
SQL> CREATE TABLE TEST AS SELECT * FROM
dba_objects WHERE 0=1;
SQL> CREATE INDEX ind_test_id ON
TEST(object_id);
SQL> INSERT INTO TEST
SELECT *
FROM dba_objects
WHERE object_id IS NOT NULL AND object_id >
10000
ORDER BY object_id DESC;
17837 rows created.
SQL> analyze table test compute statistics for
table for all columns for all indexes;
Table analyzed.
SQL> set autotrace trace;
SQL> select object_id from test;
17837 rows selected.
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT ptimizer=CHOOSE (Cost=68 Card=17837
Bytes=71348)
1 0 TABLE ACCESS (FULL) OF 'TEST' (Cost=68 Card=17837
Bytes=71348)
这时候Oracle会选择全表扫描,因为
object_id 列默认是可以为null的,来修改成 not null:
SQL>alter table test modify(object_id not
null);
SQL> select object_id from test;
17837 rows selected.
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT ptimizer=CHOOSE (Cost=11 Card=17837
Bytes=71348)
1 0 INDEX (FAST FULL SCAN) OF 'IND_TEST_ID' (NON-UNIQUE)
(Cost=11 Card=17837 Bytes=71348)
当然我们也可以使用index full scan:
SQL> select object_id from test;
17837 rows selected.
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT ptimizer=CHOOSE (Cost=41 Card=17837
Bytes=71348)
1 0 INDEX (FULL SCAN) OF 'IND_TEST_ID' (NON-UNIQUE) (Cost=101
Card=17837 Bytes=71348)
我们看到了两者都可以在这种情况下使用,那么他们有什么区别呢?有个地方可以看出两者的区别,来看一下两者的输出结果,为了让大家看清楚一点,我们只取10行。
INDEX FAST FULL SCAN
SQL> select object_id from test where
rownum<11;
OBJECT_ID
----------
66266
66267
66268
66269
66270
66271
66272
66273
66274
66275
10 rows selected.
INDEX FULL SCAN
SQL> select object_id from test where
rownum<11;
OBJECT_ID
----------
10616
12177
12178
12179
12301
13495
13536
13539
13923
16503
10 rows selected.
可以看到两者的结果完全不一样,这是为什么呢?这是因为当进行index full
scan的时候oracle定位到索引的root block,然后到branch
block(如果有的话),再定位到第一个leaf block, 然后根据leaf
block的双向链表顺序读取。它所读取的块都是有顺序的,也是经过排序的。
而index fast
full scan则不同,它是从段头开始,读取包含位图块,root block,所有的branch block,
leaf block,读取的顺序完全有物理存储位置决定,并采取多块读,没次读取db_file_multiblock_read_count个块。
使用这两种索引扫描需要表的索引字段至少有一个是not null限制。
快速全索引扫描比普通索引扫描速度快是因为快速索引扫描能够多块读取,并且能并行处理。
普通快速索引扫描可以减少排序操作。
6)Index Join索引连接
我们都知道表与表之间可以连接,那么索引与索引之间呢?当然也可以。索引连接是一种索引之间的hash连接,当查找的字段都已经包括在索引中时,就不需要去访问表的内容,直接通过访问多个索引就能得到结果。Index join只能在CBO使用的情况下进行。
3.
3.1优化器模式
3.2访问Table的方式
ORACLE
a.
b.
其中ORACLE对索引又有两种访问模式.
a)索引唯一扫描
大多数情况下,
例如:
表LOADING有两个索引
SELECT
FROM
WHERE
WHERE
1.
2.
WHERE
WHERE子句条件包括一系列值,
低一些.
例2:
WHERE
这个SQL的执行分两步,
SELECT
WHERE
在这种情况下,ORACLE将使用全表扫描.
3.3 SQL调优的本质就是调整执行计划。
在好多情况下,oracle自动选择的执行计划并不是最优的,这时需要我们人工去干预。(什么是执行计划?)
对SQL调优基本步骤:
a) 捕获SQL语句
b) 产生SQL语句的执行计划;
c) 验证统计信息(SQL语句涉及到的表格是否做过分析),表格信息(结果集的记录数,索引),字段上面数据分布特点
d) 通过手工收集到的信息,形成自己理想的执行计划。
e) 如果做过分析,则重新分析相关表格或者做柱状图分析。
f) 如果没有做过分析,则通过尝试不同的Hint,从而获得合适的执行计划。
g) 当我们正常无法调优到位时,可以打开10053事件打开优化器的跟踪,看看Oracle如何选择的.
alter
3.4 如何捕获SQL语句
捕获SQL语句的方法有如下几种:
1.SQL
2.PERFSTAT性能统计包,使用方法见附录二。
3.V$SQL,V$SESSION_WAIT,V$SQL_TEXT
3.5 如何查看执行计划
查看SQL语句的执行计划有以下几种:
1.Set
2.Explain
@?/rdbms/admin/utlxpls.sql
3.V$SQL_PLAN视图
column
column
column
column
column
select