您的位置:首页 > 数据库 > Oracle

学习《Oracle 9i10g编程艺术》的笔记 (十三) 索引

2009-12-02 10:05 274 查看
1.概述

索引是应用设计和开发的一个重要方面。如果有太多的索引,DML 的性能就会受到影响。如果索引太
少,又会影响查询(包括插入、更新和删除)的性能。要找到一个合适的平衡点,这对于应用的性能至关
重要。

我常常发现,人们在应用开发中总是事后才想起索引。我坚持认为这是一种错误的做法。如果你知
道数据将如何使用,从一开始就应该能提出应用中要使用怎样的索引,即具有一组代表性的索引。不过,
一般的做法却往往是随应用“放任自流“,过后才发现哪里需要索引,这种情况实在太多了。这说明,你
没有花时间来了解数据将如何使用以及最终要处理多少行。经过一段时间后,随着数据量的增长,你会不
停地向系统增加索引(也就是说,你所执行的是一种反应式调优)。你就有一些冗余而且从不使用的索引,
这不仅会浪费空间,还会浪费计算资源。磨刀不误砍柴工,如果刚开始的时候花几个小时好好地考虑何时
为数据加索引,以及如何加索引,这肯定能在以后的”调优“中节省更多的时间(注意,我所说的是”肯
定能“节省更多时间,而不只是”可能“节省更多时间)。

2.B*树索引

B*树索引就是我所说的“传统“索引,这是数据库中最常用的一类索引结构。其实现与二叉查找树很
相似。其目标是尽可能减少Oracle 查找数据所花费的时间。不严格地说,如果在一个数字列上有一个索引,
那么从概念上讲这个结构可能如图11.-1 所示。



例如,如果想在索引中找到值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 升序排序。而在惟一索引中,数据只按索引键排序。

3.视图能使用索引吗?

视图只是为了方便最终用户或程序
员,优化器还是会对基表使用查询。使用视图时,完全可以考虑使用为基表编写的查询中所能使用的所有
索引。“对视图建立索引”实际上就是对基表建立索引。

4.Null 和索引能协作吗?

ops$tkyte@ORA10GR1> create table t ( x int, y int );

ops$tkyte@ORA10GR1> create unique index t_idx on t(x,y);

ops$tkyte@ORA10GR1> insert into t values ( 1, 1 );

ops$tkyte@ORA10GR1> insert into t values ( 1, NULL );

ops$tkyte@ORA10GR1> insert into t values ( NULL, 1 );

ops$tkyte@ORA10GR1> insert into t values ( NULL, NULL );

ops$tkyte@ORA10GR1> analyze index t_idx validate structure;

ops$tkyte@ORA10GR1> select name, lf_rows from index_stats;
NAME LF_ROWS
------------------------------ ----------
T_IDX 3

这个表有4 行,而索引只有3 行。前三行(索引键元素中至少有一个不为null)都在索引中。最后
一行的索引键是(NULL,NULL),所以这一行不在索引中。倘若索引是一个惟一索引(如上所示),这就是可
能产生混淆的一种情况。考虑以下2 个INSERT 语句的作用:

ops$tkyte@ORA10GR1> insert into t values ( NULL, NULL );

1.row created.
ops$tkyte@ORA10GR1> insert into t values ( NULL, 1 );
insert into t values ( NULL, 1 )
*
ERROR at line 1:
ORA-00001: unique constraint (OPS$TKYTE.T_IDX) violated

ops$tkyte@ORA10GR1> select x, y, count(*)
2 from t
3 group by x,y
4 having count(*) > 1;
X Y COUNT(*)
---------- ---------- ----------
2

看上去好像不可能的,如果考虑到所有null 条目,这就说明我们的惟一键并不惟一。事实上,在Oracle
中,考虑惟一性时(NULL,NULL)与(NULL,NULL)并不相同,这是SQL 标准要求的。不过对于聚集来说
(NULL,NULL)和(NULL,NULL)则认为是相同的。两个(NULL,NULL)在比较时并不相同,但是对GROUP BY
子句来说却是一样的。所以应当考虑到:每个惟一约束应该至少有一个确实惟一的NOT NULL 列。

select * from T where x is null;这个查询无法使用我们刚才创建的索引,(NULL,NULL)行并不在索引中。

5.外键是否应该加索引?

外键未加索引是我所遇到的导致死锁的最主要的原因;这是因为,无论是更新父表主键,或者删
除一个父记录,都会在子表中加一个表锁(在这条语句完成前,不允许对子表做任何修改)。这就会不必要
地锁定更多的行,而影响并发性。人们在使用能自动生成SQL 来修改表的某个工具时,就经常遇到这种问
题。这样的工具会生成一个更新语句,它将更新表中的每一列,而不论这个值是否被UPDATE 语句修改。这
就会导致更新主键(即使主键值其实从未改变过)

6.为什么没有使用我的索引?

6.1我们在使用一个B*树索引,而且谓词中没有使用索引的最前列。如果是这种情况,可以假设有一个
表T,在T(X,Y)上有一个索引。我们要做以下查询:SELECT * FROM T WHERE Y = 5。此时,优化器就不打
算使用T(x,y)上的索引,因为谓词中不涉及X 列。在这种情况下,倘若使用索引,可能就必须查看每一个
索引条目(稍后我们会讨论一种索引跳跃式扫描,这是一种例外情况),而优化器通常更倾向于T 对做一个
全表扫描。但这并不完全排除使用索引。如果查询是SELECT X, Y FROM T WHERE Y = 5,优化器就会注意
到,它不必全面扫描表来得到X 或Y(X 和Y 都在索引中),对索引本身做一个快速的全面扫描会更合适,
因为这个索引一般比底层表小得多。

6.2对于一个有索引的列,做以下查询:

select * from t where f(indexed_column) = value

却发现没有使用INDEX_COLUMN 上的索引。原因是这个列上使用了函数。我们是对INDEX_COLUMN 的值
建立了索引,而不是对F(INDEXED_COLUMN)的值建索引。在此不能使用这个索引。如果愿意,可以另外对
函数建立索引。

6.3我们已经对一个字符创建了索引。这个列只包含数值数据。如果所用以下语句来查询:

select * from t where indexed_column = 5

注意查询中的数字5 是常数5(而不是一个字符串),此时就没有使用INDEX_COLUMN 上的索引。这是
因为,前面的查询等价于一些查询:

select * from t where to_number(indexed_column) = 5

我们对这个列隐式地应用了一个函数,如情况3 所述,这就会禁止使用这个索引。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: