索引概述
索引是一种与表或表簇相关联的可选结构,有时可以提高数据访问速度。
索引是一种模式对象,它在逻辑上和物理上都与其相关联的对象中的数据保持独立。因此,可以删除或创建索引而不会实际影响被索引的表
打个比方,假设一个人力资源经理有一个搁放纸盒的架子。在框中包含雇员信息的文件夹随机放置在纸盒中。员工 Whalen (ID 200) 的文件夹是从 10 盒从底往上的盒子中的第 1 盒,而 King 是(ID 100) 的文件夹是从底往上的第 3 盒。要查找某个文件夹,经理从底往上查看这些盒子中的每个文件夹,看完一盒又看下一盒,直到找到该文件夹。为加快查找速度,经理可创建一个索引,按顺序列出每个雇员 ID 和它的文件夹位置:
ID 100: Box 3, position 1 (bottom)
ID 101: Box 7, position 8
ID 200: Box 1, position 10
.
.
.
同样,经理可以为雇员姓氏,部门 id 等创建单独的索引。
索引的优缺点
索引的存在与否,不需要改变任何 SQL 语句的写法。
索引是对单行数据的快速访问路径。它只影响执行的速度。给定一个已索引的数据值,索引将直接指向包含该值的行的位置。
当一个索引存在于表的一个或多个列上时,数据库可以在一些情况下从随机分布的表行中检索一小部分行。索引是减少磁盘 I/O 的许多手段之一。如果一个堆组织表没有索引,数据库必须执行全表扫描来查找值。例如,如果没有索引,为查询 hr.departments 表中的位置 2700,数据库需要搜索每个表块中的每一行,以找到该值。当数据量增加时,这种方法不具备良好的可扩展性。
索引的缺点包含如下项:
- 手动创建索引通常需要对数据模型、应用程序和数据分布有深入的了解。
- 随着数据的变化,您必须重新考虑以前关于索引的决策。索引可能不再有用,或者可能需要新的索引。
- 索引占用磁盘空间。
- 当索引数据上发生DML时,数据库必须更新索引,这会造成性能开销。
通常,在下列情况下可以考虑在某列上创建索引:
- 要索引的列经常被查询,并只返回表中的行的总数的一小部分。
- 在索引的列或列集上存在引用完整性约束。索引可以避免当你更新父表主键、合并父表、从父表删除行时可能引起的全表锁定。
- 要在表上设置唯一键约束,并且您想手动指定索引和所有索引选项。
索引可用性和可见性
索引可用(默认)或不可用,可见(默认)或不可见。
这些属性的定义如下:
- 可用性
不可用索引在 DML 操作中不会被维护,并会被优化程序忽略。不可用索引可以提高大容量加载的性能。你不用删除索引并稍后重新创建它,你可以使索引不可用,最后再重新生成它。不可用索引或索引分区不会占用空间。当你把一个可用的索引置为不可用时,数据库将删除其索引段。 - 可见性
不可见索引在 DML 操作中会被维护,但在默认情况下优化程序不会使用它。使索引不可见是使其不可用或删除它的一种替代方法。不可见索引有时特别有用,比如在删除索引前测试移除后果,或临时用一下索引而不会影响整个应用程序。
键和列
键是一个列集或表达式,您可以在它上面创建索引。
虽然这两个术语通常互换使用,但索引和键还是不同的。索引是存储在数据库中的一种结构,用户使用 SQL 语句来管理它们。而键严格来讲只是一个逻辑概念
下面的语句在示例表 oe.orders 的列 customer_id 上创建索引:
CREATE INDEX ord_customer_ix ON orders (customer_id);
在前面的语句中,customer_id 列是索引键。索引本身被命名为 ord_customer_ix。
复合索引
复合索引,也称为连接索引,是在某个表中的多个列上的索引。
复合索引中的列应该以在检索数据的查询中最有意义的顺序出现,但在表中不必是相邻的。
若 WHERE 子句引用了复合索引中的所有列或前导列,复合索引可以加快SELECT 语句的数据检索速度。所以,在定义中所使用的列顺序很重要。一般地,最常被访问的列放在前面。
例如,假定应用程序经常查询雇员表中的 last_name、job_id 和薪金列。同时假定 last_name 有很高的基数,也就是相对于表的总行数来说,不同值的数目很大。按以下的列顺序创建索引:
CREATE INDEX employees_ix
ON employees (last_name, job_id, salary);
如果查询会访问所有的三个列,或仅 last_name 列,或仅 last_name 和job_id 列,则会使用此索引。在本例中,不访问 last_name 列的查询,将不会使用索引。
当多个索引满足以下任一条件时,它们可以以相同的列顺序存在于同一个表中:
- 索引的类型不同。
例如,您可以在相同的列上创建位图和 B 树索引。 - 索引使用不同的分区方案。
例如,您可以创建本地分区的索引和全局分区的索引。 - 索引具有不同的唯一性。
例如,您可以在同一列集上创建惟一索引和非惟一索引。
例如,对于相同的表列,可以以相同的顺序存在非分区索引、全局分区索引和本地分区索引。在同一时间内,只有一个列数相同、顺序相同的索引是可见的。
此功能使您能够迁移应用程序,而不需要删除现有索引并使用不同的属性重新创建它。此外,当索引键不断增加,导致数据库将新条目插入同一组索引块时,此功能在OLTP数据库中非常有用。为了缓解这种“热点”,可以将索引从非分区索引演化为全局分区索引。
如果同一列集中的索引在类型或分区模式上没有差异,那么这些索引必须使用不同的列排列。例如,下面的 SQL 语句指定的排列都是有效的:
CREATE INDEX employee_idx1 ON employees (last_name, job_id);
CREATE INDEX employee_idx2 ON employees (job_id, last_name);
唯一索引和非唯一索引
索引可以是唯一的或非唯一的。唯一索引保证在表的键列或键列集上没有具有重复的值的行。
例如,没有任何两名雇员,可以有相同的雇员 id。在一个唯一索引中,对每个数据值都存在一个 rowid。叶块中的数据仅根据键排序。
非唯一索引允许在索引的列或列集中有重复的值。例如,雇员表的first_name 列中可能包含多个 Mike 值。对于非唯一索引,rowid 被包含在键中且已排序,因此非唯一索引按索引键和 rowid (升序) 进行排序。
除了位图索引或簇键列值为空的情况之外,Oracle 数据库不会索引所有键列都为空的表行。
索引类型
Oracle 数据库提供了几种索引模式,以提供增强性能的功能。
这是索引的标准类型。他们对于主键和高选择性索引(每个索引条目对应的行很少)非常适合。在复合索引中使用时,B-树索引可以按多个索引列以排序方式检索数据。b树索引的子类型如下表所示。
| B-Tree 索引子类 | 描述 | 参考 |
|---|---|---|
| 索引组织表 | 索引组织表不同于堆组织表,因为数据本身就是索引。 | “Overview of Index-Organized Tables” |
| 反键索引 | 在这种类型的索引中,索引键中的字节被反转了,例如,103 被存储为 301。反转字节可以把对索引记录的插入分散到的很多数据块。 | “Reverse Key Indexes” |
| 降序索引 | 这种类型的索引将存储在一个特定的列或多列中的数据按降序排序。 | “Ascending and Descending Indexes” |
| B-树簇索引 | 这种类型的索引将数据按降序存储在一个或多个特定的列上。 | “Ascending and Descending Indexes” |
下表显示了不使用 B 树结构的索引类型。
| 类型 | 描述 | 参考 |
|---|---|---|
| 位图索引和位图联接索引 | 在位图索引中,索引条目使用位图来指向多个行。相比之下,B-树索引条目指向单个行。位图联接索引是在两个或更多表的联接上的位图索引。 | “Overview of Bitmap Indexes” |
| 基于函数的索引 | 这种类型的索引包括经过一个函数(如 UPPER 函数)转换过的列,或包括在表达式中的列。B-树索引或位图索引都可以是基于函数的。 | “Overview of Function-Based Indexes” |
| 应用程序域索引 | 这种类型的索引是由用户为一个特定的应用程序域中的数据创建的。其物理索引不需要使用传统的索引结构,可以存储为 Oracle 数据库表,或外部文件。 | “Overview of Application Domain Indexes” |
数据库如何维护索引
创建索引之后,数据库将自动维护和使用索引。
索引自动将数据更改反映到其底层表。更改的示例包括添加、更新和删除行。不需要用户操作。
即使插入行,索引数据的检索性能也几乎保持不变。但是,表上存在许多索引会降低DML性能,因为数据库还必须更新索引。
索引存储
Oracle 数据库将索引数据存储在一个索引段中。
在一个数据块中对索引数据可用的空间是,数据块大小减去块开销、条目开销、rowid 、和每个被索引的值所需的一个字节。
索引段的表空间,是所有者的默认表空间,或在 CREATE INDEX 语句中明确指定的表空间。为了便于管理,您可以在一个独立于表所在表空间的单独表空间中存储索引。例如,您可以选择不备份仅包含索引的表空间,因为索引表空间可以重建,这样可以减少备份所需要的时间和存储空间。
B-树索引概述
平衡树,简称 B-树,是最常见的数据库索引类型。一个 B-树索引是被划分为多个范围的已排序的值列表。通过将键与一行或行范围关联起来,B-树可以对多种类型的查询提供优秀的检索性能,包括精确匹配和范围搜索等。
下图显示了一个 B-树索引的结构。该示例显示在 department_id 列上的一个索引,它是雇员表的一个外键。

分支块和页块
B-树索引有两种类型的块: 用于查找的分支块和用于存储值的叶块。B-树索引的上层分支块包含指向下层索引块的索引数据。
在图 3-1 中,根分支块包含条目 0-40,指向下一个分支级别中最左边的块。此分支块包含条目如 0-10 和 11-19 等。每个这些条目指向包含在该范围内键值的叶块。
B-树索引之所以是平衡的,是因为所有叶块都自动处于相同的深度。因此,在索引中从任意位置检索任意记录需要的时间基本上是相同的。索引的高度是从根块到叶块所需的块的数量。分支级别等于其高度减 1。在图 3-1 中,索引的高度为 3,分支级别为 2。
分支块用于存储在两个键之间作出分支决定所需的最小键前缀。这种技术使数据库在每个分支块上能够尽可能存放更多的数据。分支块包含一个指针,该指针指向包含该键的子块。键和指针的数量受限于块大小。
叶块包含每个被索引的数据值,和一个相应的用来定位实际行的 rowid。每个条目按 (rowid,键) 排序。在一个叶块内,键和 rowid 链接到其左右同级条目。这些叶块本身也双向链接在一起。如图 3-1,最左边的叶块 (0-10)链接到第二个叶块 (11-19)。
索引扫描
在索引扫描中,数据库使用语句指定的索引列,通过遍历索引来检索行。数据库扫描索引,将使用 n 个 I/O 就能找到其要查找的值,其中 n 即是 B-树索引的高度。这是数据库索引背后的基本原理。
如果 SQL 语句仅访问被索引的列,那么数据库只需直接从索引中读取值,而不用读取表。如果该语句同时还需要访问除索引列之外的列,那么数据库会使用 rowids 来查找表中的行。通常,为检索表数据,数据库以交替方式先读取索引块,然后读取相应的表块。
完全索引扫描
在完全索引扫描中,数据库顺序读取整个索引。如果在 SQL 语句中的谓词(WHERE 子句) 引用了一个索引列,或者在某些情况下未不指定任何谓词,此时可能使用完全索引扫描。完全扫描可以消除排序,因为数据本身就是基于索引键排过序的。
例 3-1 全索引扫描
假设应用程序运行以下查询:
SELECT department_id, last_name, salary
FROM employees
WHERE salary > 5000
ORDER BY department_id, last_name;
在这个实例中,department_id last_name,和 salary 是一个复合索引键。Oracle 数据库会执行完全索引扫描,按顺序读取 (按部门 ID 和姓氏顺序) 并基于薪金属性进行筛选。通过这种方式,数据库只需扫描一个小于雇员表的数据集,而不用扫描那些未包含在查询中的列,并避免了对该数据进行排序。
完全扫描可能这样读取索引条目,如下所示:
50,Atkinson,2800,rowid 60,Austin,4800,rowid 70,Baer,10000,rowid 80,Abel,11000,rowid 80,Ande,6400,rowid 110,Austin,7200,rowid . . .
快速完全索引扫描
快速完全索引扫描是一种完全索引扫描,数据库仅访问索引本身中的数据,而无需访问表。数据库并不按特定的顺序读取索引块。
当满足以下两个条件时,快速全索引扫描可以替代全表扫描:
- 索引必须包含查询所需的所有列。
- 查询结果集中不能出现包含所有空值的行。要保证该结果,索引中至少有一列必须具有以下两种情况:
- NOT NULL 约束
- 应用于列的谓词,避免查询结果集中考虑 null
例 3-2 快速全索引扫描
例如,应用程序发出以下查询,不包含 ORDER BY 子句:
SELECT last_name, salary
FROM employees;
如果 last_name 列有一个 not null 约束。如果姓氏和工资是一个复合索引键,那么快速完全索引扫描只需读取索引条目,就可以获取所需的信息:
Baida,2900,rowid Atkinson,2800,rowid Zlotkey,10500,rowid Austin,7200,rowid Baer,10000,rowid Austin,4800,rowid . . .
索引范围扫描
索引范围扫描是索引的有序扫描,其中条件中指定了一个或多个索引前导列,一个索引键可能对应 0 个、1 个或更多个值。
条件指定一个或多个表达式和逻辑 (布尔) 运算符的组合,并返回一个值(TRUE、FALSE,或 UNKNOWN) 。
通常,数据库使用索引范围扫描来访问选择性的数据。选择性是查询所选择的数据占总行数的百分比,0 意味着没有任何行,1 表示所有行。选择性与查询谓词绑定,例如 WHERE last_name LIKE ’ a %’,或谓词的组合。当值接近 0 时,谓词的选择性更强;当值接近1时,谓词的选择性更弱(或更非选择性)。
例如,用户查询姓氏以 A 开头的雇员。假定 last_name 列已被索引,其索引条目如下所示:
Abel,rowid
Ande,rowid
Atkinson,rowid
Austin,rowid
Austin,rowid
Baer,rowid
.
.
.
数据库可以使用范围扫描,因为在谓词中指定了 last_name 列,而且每个索引键可能对应多个 rowids。例如有两个雇员名叫 Austin,所以有两个 rowids 都与索引键 Austin 相关联。
索引范围扫描可以在两边都有边界,比如部门 ID 在 10 至 40 之间的查询,或只在一边有界,比如部门 id 在 40 以上的查询。为扫描索引,数据库将在叶块之间前后移动。例如,对于 ID 在 10 到 40 之间的扫描,将先定位到包含最低键值(大于或等于 10) 的第一个索引叶块。然后顺着各个被链接的页结点水平推进,直到它找到一个大于 40 的值为止。
唯一索引扫描
相对于索引范围扫描,唯一索引扫描必须是 有 0 个 或 1 个 rowid 与索引键相关联。
当一个谓词使用相等运算符引用了唯一索引键的所有列时,数据库将执行唯一扫描。只要找到了第一个记录,唯一索引扫描就停止处理,因为不可能有第二个记录满足条件。
作为演示,假定用户运行如下查询:
SELECT *
FROM employees
WHERE employee_id = 5;
假定 employee_id 列是主键,并具有如下的索引条目:
1,rowid
2,rowid
4,rowid
5,rowid
6,rowid
.
.
.
在这种情况下,数据库可以使用唯一索引扫描来定位 rowid,以找到 ID 为 5 的雇员
索引跳跃扫描
索引跳跃扫描使用复合索引的逻辑子索引。数据库“跳跃地” 通过单个索引,好像它在多个单独的索引中搜索一样。
如果在复合索引前导键列中有少量不同值,而在非前导键列中有大量不同值,此时使用跳跃扫描是有益的。
例 3-3 复合索引跳跃扫描
假定您要在 sh.customers 表中查找一个客户,运行如下查询:
SELECT * FROM sh.customers WHERE cust_email = 'Abbey@company.example.com';
customers 表中有一列 cust_gender,其值为 M 或 F。假定在列 cust_gender 和 cust_email 上存在一个复合索引。如下例子显示了索引条目的一部分。
F,Wolf@company.example.com,rowid
F,Wolsey@company.example.com,rowid
F,Wood@company.example.com,rowid
F,Woodman@company.example.com,rowid
F,Yang@company.example.com,rowid
F,Zimmerman@company.example.com,rowid
M,Abbassi@company.example.com,rowid
M,Abbey@company.example.com,rowid
虽然未在 WHERE 子句中指定 cust_gender,数据库可以使用跳跃索引扫描。
逻辑子索引的数目决定于前导列中的非重复值的数目。在上述例子中,前导列中有两个可能值。数据库在逻辑上将该索引拆分为一个具有 F 键的子索引和另一个具有 M 键的子索引。
当搜索电子邮件为 Abbey@company.com 的客户的记录时, 数据库首先搜索 F值的子索引,然后搜索 M 值的子索引。从概念上讲,数据库这样处理查询,如下所示:
SELECT * FROM sh.customers WHERE cust_gender = 'F'
AND cust_email = 'Abbey@company.example.com'
UNION ALL
SELECT * FROM sh.customers WHERE cust_gender = 'M'
AND cust_email = 'Abbey@company.example.com';
索引聚簇因子
索引聚簇因子用于测量相对于某个索引值(如雇员姓氏) 的行顺序。随着有序程度的增加,聚簇因子随之减少。
作为一种粗略测量通过索引读取整个表所需的 I/O 数,聚簇因子非常有用:
- 如果聚簇因子较高,则在大型索引范围扫描过程中,数据库将执行相对较高数目的 I/O。索引条目指向随机表块,因此数据库可能必须一遍又一遍地来回重读索引所指向的同一数据块。
- 如果聚簇因子较低,则在大型索引范围扫描过程中数据库将执行相对较低数目的 I/O。在一个范围内的索引键倾向于指向相同的数据块,因此该数据库不必来回重读相同的数据块。
聚簇因子与索引扫描关系密切,因为它可以显示:
- 数据库是否会在大范围扫描中使用索引
- 相对于索引键的表组织程度
- 如果行必须按索引键顺序排列,是否应考虑使用索引组织表、分区或表簇
例 3-4 聚簇因子
假定雇员表存放在两个数据块中。表 3-3 描述了两个数据块中的行(省略号表示未显示的数据)。
| 数据块 1 | 数据块 2 |
|---|---|
|
|
行以姓氏顺序(以粗体显示)存储在块中。例如,数据块 1 的最下面一行描述了 Abel, 往上一行描述了 Ande,如此等等,按字母顺序直到最上面的一行描述了 Steven King。数据块 2 中的最下面行描述了 Kochar,往上一行描述了 Kumar,如此等等,按字母顺序直到最后一行描述了 Zlotkey。
假设在姓氏列上存在一个索引。每个姓氏条目对应于一个 rowid。从概念上讲,索引条目看起来如下所示:
Abel,block1row1 Ande,block1row2 Atkinson,block1row3 Austin,block1row4 Baer,block1row5 . . .
假设在雇员 ID 列上存在另一个单独的索引。从概念上讲,索引条目可能看起来像下面这样,雇员 id 几乎分布在这整个两个数据块的任意位置:
100,block1row50
101,block2row1
102,block1row9
103,block2row19
104,block2row39
105,block1row4
.
.
.
下面语句所示的查询,通过 ALL_INDEXES 查看这两个索引的聚簇因子。
SQL> SELECT INDEX_NAME, CLUSTERING_FACTOR
2 FROM ALL_INDEXES
3 WHERE INDEX_NAME IN ('EMP_NAME_IX','EMP_EMP_ID_PK');
INDEX_NAME CLUSTERING_FACTOR
-------------------- -----------------
EMP_EMP_ID_PK 19
EMP_NAME_IX 2
EMP_NAME_IX 的聚簇因子较低,这意味着在一个单一叶块中的相邻索引条目倾向于指向同一个数据块中的行。EMP_EMP_ID_PK 的聚簇因子较高,这意味着在相同的叶块中的相邻索引条目不太可能指向同一个数据块中的行。
反向键索引
反向键索引是一种 B-树索引,它在物理上反转每个索引键的字节,但保持列顺序不变。
例如,如果索引键是 20,并且在一个标准的 B-树索引中此键被存为十六进制的两个字节 C1 15,那么反向键索引会将其存为 15 C1。
反向键解决了在 B-树索引右侧的的叶块争用问题。在 Oracle 真正应用集群(Oracle RAC) 数据库中的多个实例重复不断地修改同一数据块时,这个问题尤为严重。例如,在订单表中,订单的主键是连续的。在集群中的一个实例添加订单 20,而另一个实例添加订单 21,每个实例都将它的键写入索引右侧的相同叶块。
在一个反向键索引中,对字节顺序反转,会将插入分散到索引中的所有叶块。例如键 20 和 21,本来在一个标准键索引中会相邻,现在存储在相隔很远的独立的块中。这样,顺序插入产生的 I/O 被更均匀地分布了。
因为存储数据时,并没有按照键列排序,因此在某些情况下,反向键格式丧失了执行索引范围扫描查询的能力。例如,如果用户发出一个订单 id 大于 20 的查询,但数据库不能从包含此 ID 的块开始扫描并沿着叶块水平推进。
升序和降序索引
对于升序索引,数据库按升序排列的顺序存储数据。默认情况下,字符数据按每个字节中包含的二进制值排序,数值数据按从小到大排序,日期数据从早到晚排序。
举一个升序索引的例子,请考虑下面的 SQL 语句:
CREATE INDEX emp_deptid_ix ON hr.employees(department_id);
Oracle 数据库对 hr.employees 表按 department_id 列进行排序。从 0 开始,按 department_id 列及相应的 rowid 值的升序顺序加载索引。使用此索引,数据库搜索已排序的 department_id 值,并使用相关联的 rowids 来定位包含所请求的 department_id 值的行。
通过在 CREATE INDEX 语句中指定 DESC 关键字,您可以创建一个降序索引。在这种情况下,索引在指定的一列或多列上按降序顺序存储数据。如果表3-3 employees.department_id 列上的索引为降序,则包含 250 的叶块会在树的左侧,而 0 在右侧。降序索引的默认搜索顺序是从最高值到最低值。
当要求查询按一些列升序而另一些列降序排序时,降序索引非常有用。例如,假定您在 last_name 列和 department_id 列上创建一个复合索引,如下所示:
CREATE INDEX emp_name_dpt_ix ON hr.employees(last_name ASC, department_id DESC);
一个对 hr.employees 用户查询,要求按姓氏以升序顺序 (A 到 Z) 而部门id 以降序 (从高到低)排序,则数据库可以使用此索引检索数据并避免额外的排序步骤。
索引压缩
为了减少索引中的空间,Oracle数据库可以使用不同的压缩算法。
前缀压缩
Oracle 数据库可以使用前缀压缩(也称为键压缩)来压缩b树索引或索引组织表中的主键列值的部分。前缀压缩可以大大减少索引消耗的空间。
未压缩的索引项有一个片段。使用前缀压缩的索引项有两个部分:一个是前缀项,这是分组项;一个是后缀项,这是惟一的或几乎惟一的部分。数据库通过在索引块中的后缀项之间共享前缀项来实现压缩。
默认情况下,一个唯一索引的前缀由除最后一列之外的所有键列组成,而非唯一索引的前缀包含所有键列。假设您在 oe.orders 表上的两列上创建一个复合唯一索引,如下所示:
CREATE UNIQUE INDEX orders_mod_stat_ix ON orders ( order_mode, order_status );
在前面的示例中,索引键可能是 online,0。rowid 存储在条目的键数据部分,而不是键本身的一部分。
或者,假设您在相同的列上创建了一个非惟一索引:
CREATE INDEX orders_mod_stat_ix ON orders ( order_mode, order_status );
假设在 order_mode 和 order_status 列中有许多重复值出现。一个索引块中的条目可能如下例所示:
online,0,AAAPvCAAFAAAAFaAAa
online,0,AAAPvCAAFAAAAFaAAg
online,0,AAAPvCAAFAAAAFaAAl
online,2,AAAPvCAAFAAAAFaAAm
online,3,AAAPvCAAFAAAAFaAAq
online,3,AAAPvCAAFAAAAFaAAt
在上例中,键前缀将由 order_mode 和 order_status 值的串联组成,如在 online,0,后缀包含在 rowid 中,如 AAAPvCAAFAAAAFaAAa。rowid 使得整个索引条目是唯一的,因为 rowid 本身在数据库中是唯一的。
如果前面示例中的索引是使用默认前缀压缩(由 COMPRESS 关键字指定)创建的,那么重复键前缀(如 online,0 和 online,2) 将会被压缩。从概念上讲,数据库按如下示例中所示实现压缩:
online,0
AAAPvCAAFAAAAFaAAa
AAAPvCAAFAAAAFaAAg
AAAPvCAAFAAAAFaAAl
online,2
AAAPvCAAFAAAAFaAAm
online,3
AAAPvCAAFAAAAFaAAq
AAAPvCAAFAAAAFaAAt
后缀条目(rowids)形成索引行的压缩版本。每个后缀条目引用一个与其存储在相同索引块中的前缀条目。
或者,创建一个压缩索引时,您也可以指定前缀长度。例如,如果指定了前缀长度为 1,那么前缀将会是 order_mode,而后缀将会是 order_status,rowid。对于示例 3-3 中的数值,索引会提取重复出现的 online 作为前缀,如下所示:
online
0,AAAPvCAAFAAAAFaAAa
0,AAAPvCAAFAAAAFaAAg
0,AAAPvCAAFAAAAFaAAl
2,AAAPvCAAFAAAAFaAAm
3,AAAPvCAAFAAAAFaAAq
3,AAAPvCAAFAAAAFaAAt
对索引中的每个叶块,一个特定前缀最多被存储一次。只有在 B-树索引的叶块中的键会被压缩。在分支块中的键后缀可以被截断,但不会被压缩。
高级索引压缩
从 Oracle Database 12c Release 1 (12.1.0.2) 开始,对于堆组织表上受支持的索引,高级索引压缩对传统的前缀压缩进行了改进。
高级索引压缩的优点
前缀压缩对所支持的索引类型、压缩比和易用性有限制。前缀压缩对每个块使用固定的重复键消除,而高级索引压缩对每个块使用自适应的重复键消除。高级索引压缩的主要优点是:
- 数据库使用许多内部算法,如列内级前缀、重复键消除和 rowid 压缩,自动为每个块选择最佳压缩。与前缀压缩不同,高级索引压缩不需要用户知道数据特征。
- 高级压缩对非惟一索引和惟一索引都有效。前缀压缩在一些非惟一索引上工作得很好,但在前导列没有很多重复的索引上,其比率较低。
- 压缩索引的使用方式与未压缩索引相同。索引支持相同的访问路径:惟一键查找、范围扫描和快速完整扫描。
- 索引可以从父表或包含表空间的表继承高级压缩。
高级索引压缩的原理
高级索引压缩在块级别工作,为每个块提供最佳的压缩。数据库使用以下技术:
- 在创建索引期间,当叶块满时,数据库会自动将该块压缩到最优级别。
- 由于 DML 而重新组织索引块时,如果数据库能够为传入的索引项创建足够的空间,则不会发生块分割。然而,在没有高级索引压缩的 DML 中,当块满时总是发生索引块分割。
高级索引压缩 HIGH 选项
在 Oracle 数据库 12c 版本 2(12.2)之前的版本中,高级索引压缩的唯一形式是 LOW 级别压缩(COMPRESS ADVANCED LOW)。现在还可以指定 high compression (COMPRESS ADVANCED HIGH),这是默认值。高级索引压缩与 HIGH 选项提供了以下优点:
- 在大多数情况下提供更高的压缩比,同时也提高了访问索引的查询的性能
- 采用比高级 LOW 级别更复杂的压缩算法
- 将数据存储在压缩单元中,压缩单元是一种特殊的磁盘上格式
例 3-5 创建具有高级HIGH级别压缩的索引
这个示例支持对表 hr.employees 上的索引进行高级索引压缩:
CREATE INDEX hr.emp_mndp_ix
ON hr.employees(manager_id, department_id)
COMPRESS ADVANCED;
下面的查询显示了压缩的类型:
SELECT COMPRESSION FROM DBA_INDEXES WHERE INDEX_NAME ='EMP_MNDP_IX';
COMPRESSION
-------------
ADVANCED HIGH
位图索引概述
在位图索引中,数据库为每个索引键存储一个位图。在传统的 B-树索引中,一个索引条目指向单个行。在位图索引中,每个索引键存储指向多个行的指针。
位图索引主要用于数据仓库,或在以特定方式引用很多列的查询环境中。可能需要一个位图索引的情况包括:
- 索引列的基数较低,也就是说,不同值的数目相比表的总行数很小。
- 被索引的表是只读的,或 DML 语句不会对其进行重大修改。
举一个数据仓库的例子,sh.customer 表的 cust_gender 列只有两个可能的值: M 和 F。假设会经常查询某一性别的客户数目。在这种情况下,可以考虑在 customer.cust_gender 列上创建位图索引。
位图中的每一位对应于一个可能的 rowid。如果设置了某位,那么与其相应的 rowid 行包含该键值。映射函数将位的位置转换为一个实际的 rowid,所以,虽然位图索引使用不同的内部表示形式,但它提供了与 B-树索引相同的功能。
如果更新了某个单行中的索引列,那么数据库将锁定整个索引键条目(例如M 或 F),而不只是该位映射到的更新行。因为一个键指向多个行,DML通常会锁定索引数据的所有这些行。因此,位图索引并不适合许多 OLTP 应用程序。
案例:单表的位图索引
在这个案例中,sh.customers 表中的某些列是创建位图索引的候选对象。
考虑以下查询:
SQL> SELECT cust_id, cust_last_name, cust_marital_status, cust_gender
2 FROM sh.customers
3 WHERE ROWNUM < 8 ORDER BY cust_id;
CUST_ID CUST_LAST_ CUST_MAR C
---------- ---------- -------- -
1 Kessel M
2 Koch F
3 Emmerson M
4 Hardy M
5 Gowen M
6 Charles single F
7 Ingram single F
7 rows selected.
cust_marital_status 和 cust_gender 列的基数较低,而 cust_id 和cust_last_name 列的基数较高。因此,在 cust_marital_status 和 cust_gender 列上创建位图索引可能是恰当的。而在其它列上创建位图索引则可能没什么用处。相反,在这些列上创建 B-树唯一索引可能会提供最有效的表示形式和检索性能。
表 3-4 说明了上例所示的 cust_gender 列的位图索引。它包括两个分别针对每个性别的单独位图。
| 值 | Row 1 | Row 2 | Row 3 | Row 4 | Row 5 | Row 6 | Row 7 |
|---|---|---|---|---|---|---|---|
M |
1 | 0 | 1 | 1 | 1 | 0 | 0 |
F |
0 | 1 | 0 | 0 | 0 | 1 | 1 |
映射函数将位图中的每一位转换为客户表中的一个 rowid。每一位的值取决于表中的相应行中的值。例如,M 值的位图其第一位包含 1,因为客户表的第一行的性别是 M。对于位图 cust_gender ='M ',与第 2、6,7 行中相应的位为 0,因为这些行上包含的值不是 M。
调查客户的人口趋势的分析人员可能会问,"我们的女性客户中,有多少是单身或离婚的? " 这一问题对应于以下的 SQL 查询:
SELECT COUNT(*)
FROM customers
WHERE cust_gender = 'F'
AND cust_marital_status IN ('single', 'divorced');
位图索引可以高效地处理此查询,通过计算得到的位图作为插图表 3-5 在 1 值的数量。要确定满足条件的客户,数据库可以访问表使用得到的位图。
| Value | Row 1 | Row 2 | Row 3 | Row 4 | Row 5 | Row 6 | Row 7 |
|---|---|---|---|---|---|---|---|
M |
1 | 0 | 1 | 1 | 1 | 0 | 0 |
F |
0 | 1 | 0 | 0 | 0 | 1 | 1 |
single |
0 | 0 | 0 | 0 | 0 | 1 | 1 |
divorced |
0 | 0 | 0 | 0 | 0 | 0 | 0 |
single 或 divorced 和 F |
0 | 0 | 0 | 0 | 0 | 1 | 1 |
位图索引可以有效地合并与 WHERE 子句中的多个条件相对应的索引。满足一些但不是全部条件的行,将在表本身被访问之前被过滤掉。这种技术往往大大提高了响应的时间。
位图联接索引
位图联接索引是建立在两个或更多表的联接之上的位图索引。
对于表列中的每个值,索引存储被索引表中的相应行的 rowid。相比之下,在标准位图索引中,索引是建立在一个表上的。
通过在联接前预先执行限制条件,位图联接索引是减少数据量的有效方式。举一个可能会用到位图联接索引的示例,假定用户经常查询某种特定职位类型的雇员数。一个典型的查询可能如下所示:
SELECT COUNT(*)
FROM employees, jobs
WHERE employees.job_id = jobs.job_id
AND jobs.job_title = 'Accountant';
上述查询通常使用 jobs.job_title 上的一个索引来检索职位表中的 Accountant 行,然后通过(公共列) 职位 ID,和员工表中 employees.job_id 列上的索引来找到匹配的行。若要从索引本身检索数 据,而不是从表中扫描,您可以创建一个位图联接索引,如下所示:
CREATE BITMAP INDEX employees_bm_idx
ON employees (jobs.job_title)
FROM employees, jobs
WHERE employees.job_id = jobs.job_id;
如下图所示,索引键是 jobs.job_title,而被索引的表是 employees。

从概念上讲,employees_bm_idx 是建立在下例(包括示例输出) 所示的 SQL 查询中的 jobs.title 列上的索引。在索引中的 job_title 键指向雇员表中的行。对会计师数目的查询可以仅使用索引,而不用访问 employees表和 jobs 表,因为该索引本身已包含所请求的信息。
SELECT jobs.job_title AS "jobs.job_title", employees.rowid AS "employees.rowid"
FROM employees, jobs
WHERE employees.job_id = jobs.job_id
ORDER BY job_title;
jobs.job_title employees.rowid
----------------------------------- ------------------
Accountant AAAQNKAAFAAAABSAAL
Accountant AAAQNKAAFAAAABSAAN
Accountant AAAQNKAAFAAAABSAAM
Accountant AAAQNKAAFAAAABSAAJ
Accountant AAAQNKAAFAAAABSAAK
Accounting Manager AAAQNKAAFAAAABTAAH
Administration Assistant AAAQNKAAFAAAABTAAC
Administration Vice President AAAQNKAAFAAAABSAAC
Administration Vice President AAAQNKAAFAAAABSAAB
.
.
.
在数据仓库中,连接条件是在维度表主键列和事实数据表的外键列之间的一个等值连接 (使用相等运算符)。位图联接索引有时比物化视图的存储效率高得多,这是一种替代提前物化连接的方法。
位图存储结构
Oracle 数据库使用一个 B-树索引结构来为每个索引键存储位图。
例如,如果 jobs.job_title 是一个位图索引的键列,那么索引数据存储在一个 B-树中。单个位图存储在叶块中。
例 3-6 位图存储实例
假定 jobs.job_title 列具有 Shipping Clerk、Stock Clerk、及其他几个唯一值。此位图索引的索引条目具有以下组件:
- 作为索引键的职位
- 一个 rowids 范围的低值 rowid 和高值 rowid
- 在该范围内的特定 rowids 的位图
从概念上讲,该索引中的一个索引叶块,可能包含如下所示的条目:
Shipping Clerk,AAAPzRAAFAAAABSABQ,AAAPzRAAFAAAABSABZ,0010000100Shipping Clerk,AAAPzRAAFAAAABSABa,AAAPzRAAFAAAABSABh,010010Stock Clerk,AAAPzRAAFAAAABSAAa,AAAPzRAAFAAAABSAAc,1001001100Stock Clerk,AAAPzRAAFAAAABSAAd,AAAPzRAAFAAAABSAAt,0101001001Stock Clerk,AAAPzRAAFAAAABSAAu,AAAPzRAAFAAAABSABz,100001...
同一职位可能出现在多个条目中,这是因为其 rowid 范围不同。
某个会话更新了一名雇员的职务 ID,从 Shipping Clerk 到 Stock Clerk。在这种情况下,该会话需要对旧值(Shipping Clerk)和新值(Stock Clerk) 所在索引键条目的独占访问权限。直到 UPDATE 提交之前,Oracle 数据库会锁定这两个条目所指向的所有行,而不是由 Accountant 或其他任何键所指向的行。
位图索引的数据存储在一个段中。Oracle 数据库将每个位图存储在一个或多个片断中。每个片断占一个单一数据块的一部分。
基于函数的索引概述
基于函数的索引计算函数或涉及一个或多个列的表达式的值,并将其存储在索引中。基于函数的索引可以是一个 B-树索引或位图索引。
用于生成索引的函数可以是算术表达式,或一个包含 SQL 函数、用户定义 PL/SQL 函数、包函数,或 C 调用的表达式。例如,函数可以将两个列中的值相加。
使用基于函数的索引
基于函数的索引对于在 WHERE 子句中包含函数计算的语句是有效的。仅当在查询中包含该函数时,数据库才使用基于函数的索引。当数据库处理 INSERT 和 UPDATE 语句时,它仍然必须计算函数才能完成对语句的处理。
例 3-7 基于算术表达式的索引
例如,假设您创建如下基于函数的索引:
CREATE INDEX emp_total_sal_idx
ON employees (12 * salary * commission_pct, salary, commission_pct);
数据库在处理以下查询(包括部分示例输出)时可以使用前面的索引:
SELECT employee_id, last_name, first_name,
12*salary*commission_pct AS "ANNUAL SAL"
FROM employees
WHERE (12 * salary * commission_pct) < 30000
ORDER BY "ANNUAL SAL" DESC;
EMPLOYEE_ID LAST_NAME FIRST_NAME ANNUAL SAL
----------- ------------------------- -------------------- ----------
159 Smith Lindsey 28800
151 Bernstein David 28500
152 Hall Peter 27000
160 Doran Louise 27000
175 Hutton Alyssa 26400
149 Zlotkey Eleni 25200
169 Bloom Harrison 24000
例 3-8 基于 SQL 函数 UPPER的索引
基于 SQL 函数 UPPER(column_name) 或 LOWER(column_name)定义的索引,使对大小写无关的搜索变得非常方便。例如,假设雇员表的 first_name列包含混合大小写字符。您在 hr.employees 表上创建了如下基于函数的索引:
CREATE INDEX emp_fname_uppercase_idx
ON employees ( UPPER(first_name) );
emp_fname_uppercase_idx 索引使如下所示的查询变得非常方便:
SELECT *
FROM employees
WHERE UPPER(first_name) = 'AUDREY';
例 3-9 为表中的特定行建立索引
基于函数的索引对于只在一个表中的特定行上建立索引也是有用的。例如 sh.customers 表中的 cust_valid 列有 I 或 A 两个值。如果只想对含 A 值的行建立索引,您可以编写一个函数,让不含 A 值的其它任何行都返回一个空值。可以这样创建索引,如下所示:
CREATE INDEX cust_valid_idx
ON customers ( CASE cust_valid WHEN 'A' THEN 'A' END );
优化基于函数的索引
对于在 WHERE 子句中包含表达式的查询,优化程序可以在一个基于函数的索引上使用索引范围扫描。
当谓词具有高度选择性,即选择相对较少的行时,范围扫描访问路径尤其有用。示例 3-7 中,如果索引是基于表达式 12*salary*commission_pct 来创建的,则优化程序可以使用索引范围扫描。
虚拟列可用于快速访问由表达式生成的数据。例如,您可以为表达式 12*salary*commission_pct 定义虚拟列 annual_sal,并在该 annual_sal 列上创建一个基于函数的索引。
优化程序通过分析在 SQL 语句中的表达式来执行表达式匹配,然后比较的语句表达式目录树和基于函数的索引。这种比较不区分大小写,并忽略空格。
应用程序域索引概述
应用程序域索引是一个特定于应用程序的自定义索引。
可扩展的索引可以:
- 可以在自定义的、复杂的数据类型(如文档、空间数据、图像和视频剪辑等)之上建立索引。
- 使用专门的索引技术
可以将应用程序特定的索引管理例程封装为 indextype 的模式对象,并在表中的列或对象类型属性上定义一个域索引。可扩展的索引可以有效地处理特定于应用程序的操作。
由称为模块的应用软件,来控制域索引的结构和内容。数据库与应用程序进行交互,来建立、维护、并搜索域索引。索引结构本身可以作为一个索引组织表存储在数据库中,或作为一个文件存储在数据库外部。
索引组织表概述
索引组织表是存储在 B-树索引结构的一种变体中的表。在堆组织表中,行被插入到适合他们的位置。
在索引组织表中,行被存储在表的主键索引中。在 B-树中的每个索引条目也存储非键列值。因此,索引即是数据,数据也是索引。同堆组织表一样,应用程序使用 SQL 语句操作索引组织表。
给索引组织表打个比喻,假设人事经理有一箱纸盒。给每个盒子贴上 1、2、3、4 等标签,但这些盒子在书架上并不是顺序排放的。相反,每个盒子包含一个指针,指示序列中的下一个盒子的位置。
包含雇员记录的文件夹存储在每个盒子中。文件夹按雇员 id 排序。员工King 的 ID 最低, 为 100,所以他的文件夹在第 1 盒的底部。雇员 101 的文件夹在 100 的之上,102 的在 101 的之上,如此等等,直到盒子 1 已满。序列中的下一个文件夹在盒子 2 的底部。
在这个比喻中,将文件夹按雇员 ID 排序,使得能够有效地搜索文件夹,而无需维护一个单独的索引。假设用户请求雇员 107、120 、和 122 的记录。人事经理不需在一个步骤中搜索索引、然后又在另一步骤中检索文件夹,他可以按顺序搜索文件夹,并同时检索每个找到的文件夹。
通过主键或有效的前缀,索引组织表提供了对表行更快的访问。非键列存在于索引叶块中,避免了额外的数据块 I/O。例如,雇员 100 的薪酬存储在索引行本身中。此外,因为行是以主键顺序存储的,通过主键或前缀进行范围访问只涉及最少的块 I/O。另一个好处是避免一个单独的主键索引的空间开销。
当有关的数据片断必须存储在一起,或数据必须按特定的顺序物理地存储。这种表的典型用途是用于信息检索、空间数据和OLAP应用程序。
索引组织表特征
数据库系统通过操作 B-树索引结构,来在索引组织表上执行所有操作。
下表总结了索引组织表和堆组织表之间的差异。
| 堆组织表 | 索引组织表 |
|---|---|
| 由 rowid 唯一地标识行。定义主键约束是可选选项。 | 主键唯一地标识行。必须定义主键约束。 |
| 允许在 ROWID 伪列中的物理 rowid 上创建辅助索引。 | 允许在 ROWID 伪列中的逻辑 rowid 上创建辅助索引。 |
| 可直接由 rowid 访问单个行。 | 可间接通过主键访问单个行。 |
| 顺序全表扫描按一定顺序返回所有行。 | 完全索引扫描或快速完全索引扫描按一定顺序返回所有行。 |
| 可与其他表一起存储在表簇中。 | 不能存储在表簇中。 |
| 可以包含一个 LONG 数据类型的列,和多个 LOB 数据类型的列。 | 可以包含 LOB 列,但不能包含 LONG 列。 |
| 可以包含虚拟列(只支持关系型堆表)。 | 不能包含虚拟列。 |
图 3-3 显示了一个索引组织表 departments 的结构。叶块包含的表行,按主键顺序排序。例如,在第一个叶块中的第一个值显示其部门 ID 20,部门名称为 Marketing、经理 ID 为 201、位置 ID 为 1800。

例 3-10 扫描索引组织表
索引组织表将所有数据都存储在相同的结构中,且不需要存储 rowid。如图 3-3,索引组织表中的叶块 1 可能包含如下按主键排序的条目:
20,Marketing,201,1800
30,Purchasing,114,1700
索引组织表中的叶块 2 可能包含如下所示的条目:
50,Shipping,121,1500
60,IT,103,1400
按主键顺序对索引组织表行的扫描,依照如下顺序读取块:
- Block 1
- Block 2
例 3-11 扫描堆组织表
为了对比堆组织表和索引组织表的数据访问,假设堆组织部门表段的 block 1 包含如下行:
50,Shipping,121,1500
20,Marketing,201,1800
Block 2包含相同表的行如下:
30,Purchasing,114,1700
60,IT,103,1400
这个堆组织表的 B-tree 索引叶块包含以下条目,其中第一个值是主键,第二个值是 rowid:
20,AAAPeXAAFAAAAAyAAD
30,AAAPeXAAFAAAAAyAAA
50,AAAPeXAAFAAAAAyAAC
60,AAAPeXAAFAAAAAyAAB
以主键顺序扫描表行,按照以下顺序读取表段块:
- Block 1
- Block 2
- Block 1
- Block 2
因此,本例中的块 I/O 数量是索引组织示例中的两倍。
索引组织表的行溢出区
在创建一个索引组织表时,您可以指定一个单独的段为行溢出区。
索引组织表的 B-树索引条目可能比较大,因为它们包含整个行,因此用一个单独的段来包含这些条目是很有用的。相比之下,常规 B-树条目则通常很小,因为它们仅包含键和 rowid。
如果指定了行溢出区,那么数据库可以将索引组织的表中的行分成以下两个部分:
- 索引条目
本部分包含所有主键列的值、指向该行溢出部分的物理 rowid、或(可选的)几个非键列的值。这部分存储在索引段中。 - 溢出部分
此部分包含剩余的非键列的值。这部分存储在溢出存储区段中。
索引组织表的辅助索引
辅助索引是一个建立在索引组织表上的索引。
在某种意义上,它是一个索引的索引。辅助索引是一个独立的模式对象,并与索引组织表分开存储。
Oracle 数据库使用叫做逻辑 rowids 的行标识符来访问索引组织表。逻辑的 rowid 是表主键的 base64 编码表示形式。逻辑 rowid 的长度取决于主键长度。
由于插入操作,索引叶块中的行可以在块内或块之间移动。索引组织表中的行像堆组织表那样迁移行。因为索引组织表中的行并没有永久的物理地址,数据库使用基于主键的逻辑 rowids。
例如,假设 departments 表是索引组织表。location_id 列中存储每个部门的 ID。表像如下所示这样存储行,最后一个值是是位置 ID:
10,Administration,200,1700
20,Marketing,201,1800
30,Purchasing,114,1700
40,Human Resources,203,2400
在 location_id 列上的辅助索引可能有类似如下的索引条目,逗号之后的值是逻辑 rowid:
1700,*BAFAJqoCwR/+
1700,*BAFAJqoCwQv+
1800,*BAFAJqoCwRX+
2400,*BAFAJqoCwSn+
辅助索引对索引组织表提供快速、高效的访问,但使用的列既不是主键也不是主键前缀。例如,查询其 ID 大于 1700 的部门的名称,可以使用辅助索引以加快数据访问。
逻辑 Rowids 和物理猜想
辅助索引使用逻辑 rowids 来查找表行。
逻辑的 rowid 包括一个物理猜测,是索引条目第一次被创建时的物理 rowid 。Oracle 数据库可以使用物理猜测,直接探入索引组织表的叶块,以绕过主键搜索。当行的物理位置更改了,即使它包含的物理猜测已经过时,其逻辑 rowid 仍然保持有效。
对于堆组织表,按辅助索引访问涉及对辅助索引的扫描,和一个读取行所在数据块的额外 I/O。对于索引组织表,按辅助索引访问取决于物理猜测的使用及其准确性:
- 不用物理猜测,则访问包括两个索引扫描: 先是一个对辅助索引的扫描,然后是一个对主键索引的扫描。
- 使用物理猜测,则访问取决于其准确性:
- 如果物理猜测准确,则访问包括一个辅助索引扫描,和一个读取行所在数据块的额外 I/O。
- 如果物理猜测不准确,则访问包括一个辅助索引扫描,和一个读取错误的数据块 I/O (即猜测所指出的),再是一个按主键值对索引组织表的唯一索引扫描
索引组织表上的位图索引
索引组织表上的辅助索引可以是位图索引。位图索引为每个索引键存储一个位图。
当索引组织表上存在位图索引时,所有位图索引都使用堆组织映射表。映射表将存储索引组织表的逻辑 rowids。每个映射表行会为相应的索引组织表行存储一个逻辑 rowid。
数据库使用搜索键来访问位图索引。如果数据库找到了该键,则该位图条目被转换为一个物理 rowid。对堆组织表,数据库使用物理 rowid 来访问基表。对索引组织表,数据库使用物理 rowid 来访问映射表,这样会生成一个逻辑 rowid,以被数据库用来访问索引组织表。图 3-4 演示了一个通过索引访问 departments_iot 表的查询。





