索引( Index )是常见的数据库对象它的设置好坏使用是否得当极大地影响数据库应用程序和Database 的性能虽然有许多资料讲索引的用法 DBA 和 Developer 们也经常与它打交道但笔者发现还是有不少的人对它存在误解因此针对使用中的常见问题讲三个问题此文所有示例所用的数据库是 Oracle OPS on HP N series 示例全部是真实数据读者不需要注意具体的数据大小而应注意在使用不同的方法后数据的比较本文所讲基本都是陈词滥调但是笔者试图通过实际的例子来真正让您明白事情的关键
一讲索引并非总是最佳选择
如果发现Oracle 在有索引的情况下没有使用索引这并不是Oracle 的优化器出错在有些情况下Oracle 确实会选择全表扫描(Full Table Scan)而非索引扫描(Index Scan)这些情况通常有
表未做statistics 或者 statistics 陈旧导致 Oracle 判断失误 根据该表拥有的记录数和数据块数实际上全表扫描要比索引扫描更快
对第种情况最常见的例子是以下这句sql 语句
select count(*) from mytable;
在未作statistics 之前它使用全表扫描需要读取多个数据块(一个数据块是k) 做了statistics 之后使用的是 INDEX (FAST FULL SCAN) 只需要读取个数据块但是statistics 做得不好也会导致Oracle 不使用索引
第种情况就要复杂得多一般概念上都认为索引比表快比较难以理解什么情况下全表扫描要比索引扫描快为了讲清楚这个问题这里先介绍一下Oracle 在评估使用索引的代价(cost)时两个重要的数据CF(Clustering factor) 和 FF(Filtering factor)
CF: 所谓 CF 通俗地讲就是每读入一个索引块要对应读入多少个数据块
FF: 所谓 FF 就是该sql 语句所选择的结果集占总的数据量的百分比
大约的计算公式是FF * (CF + 索引块个数) 由此估计出一个查询 如果使用某个索引会需要读入的数据块块数需要读入的数据块越多则 cost 越大Oracle 也就越可能不选择使用 index (全表扫描需要读入的数据块数等于该表的实际数据块数)
其核心就是 CF 可能会比实际的数据块数量大CF 受到索引中数据的排列方式影响通常在索引刚建立时索引中的记录与表中的记录有良好的对应关系CF 都很小在表经过大量的插入修改后这种对应关系越来越乱CF 也越来越大此时需要 DBA 重新建立或者组织该索引
如果某个sql 语句以前一直使用某索引较长时间后不再使用一种可能就是 CF 已经变得太大需要重新整理该索引了
FF 则是Oracle 根据 statistics 所做的估计比如 mytables 表有万行其主键myid的最小值是最大值是考虑以下sql 语句
Select * from mytables where myid>=; 和
Select * from mytables where myid>=
这两句看似差不多的 sql 语句对Oracle 而言却有巨大的差别因为前者的 FF 是% 而后者的 FF 可能只有 %如果它的CF 大于实际的数据块数则Oracle 可能会选择完全不同的优化方式而实际上在我们的数据库上的测试验证了我们的预测 以下是在HP 上执行时它们的 explain plan:
第一句
SQL> select * from mytables where myid>=;
已选择行
Execution Plan
SELECT STATEMENT Optimizer=CHOOSE (Cost= Card= Byt es=)
TABLE ACCESS (FULL) OF MYTABLES (Cost= Card= Byt es=)
Statistics
recursive calls
db block gets
consistent gets
physical reads
redo size
bytes sent via SQL*Net to client
bytes received via SQL*Net from client
SQL*Net roundtrips to/from client
sorts (memory)
sorts (disk)
rows processed
第二句
Execution Plan
SELECT STATEMENT Optimizer=CHOOSE (Cost= Card= Bytes= )
TABLE ACCESS (BY INDEX ROWID) OF MYTABLES (Cost= Card=
Bytes=)
INDEX (RANGE SCAN) OF PK_MYTABLES (UNIQUE) (Cost= Card=)
Statistics
recursive calls
db block gets
consistent gets
physical reads
redo size
bytes sent via SQL*Net to client
bytes received via SQL*Net from client
SQL*Net roundtrips to/from client
sorts (memory)
sorts (disk)
rows processed
显而易见第句没有使用索引第句使用了主键索引pk_mytables FF的巨大影响由此可见一斑由此想到我们在写sql 语句时如果预先估计一下 FF 你就几乎可以预见到 Oracle 会否使用索引
二讲索引也有好坏
索引有 B tree 索引 Bitmap 索引 Reverse b tree 索引 等最常用的是 B tree 索引 B 的全称是Balanced 其意义是从 tree 的 root 到任何一个leaf 要经过同样多的 level 索引可以只有一个字段(Single column) 也可以有多个字段(Composite)最多个字段I 还支持 Functionbased index 许多developer 都倾向于使用单列B 树索引
所谓索引的好坏是指
索引不是越多越好特别是大量从来或者几乎不用的索引对系统只有损害OLTP系统每表超过个索引即会降低性能而且在一个sql 中 Oracle 从不能使用超过 个索引
很多时候单列索引不如复合索引有效率
用于多表连结的字段加上索引会很有作用
那么在什么情况下单列索引不如复合索引有效率呢?有一种情况是显而易见的那就是当sql 语句所查询的列全部都出现在复合索引中时此时由于 Oracle 只需要查询索引块即可获得所有数据当然比使用多个单列索引要快得多(此时这种优化方式被称为 Index only access path)
除此之外呢?我们还是来看一个例子吧
在 HP(Oracle ) 上执行以下语句
select count() from mytabs where coid>= and issuedate >= to_date ( yyyymmdd)
一开始我们有两个单列索引I_mytabs(coid) I_mytabs(issuedate) 下面是执行情况
COUNT()
Execution Plan
SELECT STATEMENT Optimizer=CHOOSE (Cost= Card= Bytes=)
SORT (AGGREGATE)
TABLE ACCESS (BY INDEX ROWID) OF T_MYTABS (Cost= Card
= Bytes=)
INDEX (RANGE SCAN) OF I_MYTABS (NONUNIQUE) (Cost=
Card=)
Statistics
recursive calls
db block gets
consistent gets
physical reads
redo size
bytes sent via SQL*Net to client
bytes received via SQL*Net from client
SQL*Net roundtrips to/from client
sorts (memory)
sorts (disk)
rows processed
可以看到它读取了个数据块来获得所查询的 多行
现在去掉这两个单列索引增加一个复合索引I_mytabs_test ( coid issuedate) 重新执行结果如下
COUNT()
Execution Plan
SELECT STATEMENT Optimizer=CHOOSE (Cost= Card= Bytes=)
SORT (AGGREGATE)
INDEX (RANGE SCAN) OF I_MYTABS_TEST (NONUNIQUE) (Cost= Card= Bytes=)
Statistics
recursive calls
db block gets
consistent gets
physical reads
redo size
bytes sent via SQL*Net to client
bytes received via SQL*Net from client
SQL*Net roundtrips to/from client
sorts (memory)
sorts (disk)
rows processed
可以看到这次只读取了个数据块
块对块这就是在这个例子中单列索引与复合索引的代价之比这个例子提示我们 在许多情况下单列索引不如复合索引有效率
可以说在索引的设置问题上其实有许多工作可以做正确地设置索引需要对应用进行总体的分析
三讲索引再好不用也是白搭
抛开前面所说的假设你设置了一个非常好的索引任何傻瓜都知道应该使用它但是Oracle 却偏偏不用那么需要做的第一件事情