5 Indexes and Index-Organized Tables(5 索引与索引组织表)

索引是可加快表行访问速度的模式对象;索引组织表是存储在索引结构中的表。

Introduction to Indexes(索引简介)

索引是与表或表簇关联的可选结构,有时可加快数据访问速度。

索引作为模式对象,在逻辑上和物理上均与其关联对象的数据相互独立。因此,创建或删除索引不会对被索引的表产生物理影响。

注意:删除索引后,应用程序仍可正常运行,但对之前已索引数据的访问速度可能会变慢。

举个类比案例:假设人力资源经理有一架子纸板箱,装有员工信息的文件夹被随机放入箱中。员工 Whalen(ID 200)的文件夹在 1 号箱底部向上数第 10 个位置,而员工 King(ID 100)的文件夹在 3 号箱底部。为查找某个文件夹,经理需从 1 号箱底部开始逐份查看,再逐个箱子查找,直至找到目标文件夹。为加快查找速度,经理可创建一份索引,按顺序列出每个员工 ID 及其文件夹位置:

  • ID 100:3 号箱,位置 1(底部)
  • ID 101:7 号箱,位置 8
  • ID 200:1 号箱,位置 10

类似地,经理还可针对员工姓氏、部门 ID 等分别创建索引。

索引简介包含的主题

  • Advantages and Disadvantages of Indexes(索引的优势与劣势)
  • Index Usability and Visibility(索引的可用性与可见性)
  • Keys and Columns(键与列)
  • Composite Indexes(复合索引)
  • Unique and Nonunique Indexes(唯一索引与非唯一索引)
  • Types of Indexes(索引类型)
  • How the Database Maintains Indexes(数据库如何维护索引)
  • Index Storage(索引存储)
Advantages and Disadvantages of Indexes(索引的优势与劣势)

是否存在索引,无需修改任何 SQL 语句的语法。

索引是访问单行数据的快速路径,仅影响执行速度。对于已索引的数据值,索引会直接指向包含该值的行的位置。

当表的一个或多个列上存在索引时,数据库在某些情况下可从表中检索少量随机分布的行。索引是减少磁盘 I/O 的多种方式之一。若堆组织表没有索引,数据库需执行全表扫描才能找到目标值。例如,查询未索引的 hr.departments 表中位置为 2700 的记录时,数据库需搜索每个块中的每一行,这种方式在数据量增加时扩展性较差。

索引的劣势包括:

  1. 手动创建索引通常需要深入了解数据模型、应用程序和数据分布;
  2. 数据发生变化时,需重新评估之前的索引决策(部分索引可能不再有用,或需要创建新索引);
  3. 索引会占用磁盘空间;
  4. 对索引数据执行 DML 操作时,数据库需同步更新索引,这会带来性能开销。

注意:从 Oracle 数据库 19c 开始,Oracle 数据库可持续监控应用程序工作负载,自动创建和管理索引。自动索引功能以数据库任务的形式实现,按固定间隔运行。

以下场景可考虑创建索引:

  1. 索引列被频繁查询,且返回的行数占表总行数的比例较小;
  2. 索引列(或多列)上存在引用完整性约束。若更新父表主键、合并到父表或从父表删除数据,索引可避免表被全表锁定;
  3. 需为表设置唯一键约束,且希望手动指定索引及所有索引选项。

另请参见:

  • 《Oracle Database Administrator’s Guide》(《Oracle 数据库管理员指南》),了解更多关于自动索引的信息;
  • 《Oracle Database Licensing Information User Manual》(《Oracle 数据库许可信息用户手册》),了解不同版本和服务支持的功能详情。
Index Usability and Visibility(索引的可用性与可见性)

索引分为可用(默认)或不可用、可见(默认)或不可见,具体定义如下:

  • 可用性(Usability)
    不可用索引会被优化器忽略,且不会被 DML 操作维护,可提升批量加载的性能。无需删除索引后重新创建,只需将索引设为不可用,后续再重建即可。不可用的索引和索引分区不占用空间,将可用索引设为不可用时,数据库会删除其索引段。

  • 可见性(Visibility)
    不可见索引会被 DML 操作维护,但优化器默认不使用。将索引设为不可见,是替代将其设为不可用或删除索引的方案,尤其适用于删除索引前测试其影响,或临时使用索引而不影响整体应用程序的场景。

另请参见:“Overview of the Optimizer”(优化器概述),了解优化器如何选择执行计划。

Keys and Columns(键与列)

键(Key)是可用于构建索引的一组列或表达式。

尽管“索引”和“键”常被互换使用,但二者本质不同:索引是存储在数据库中、用户可通过 SQL 语句管理的结构;键则纯粹是逻辑概念。

以下语句在示例表 oe.orderscustomer_id 列上创建索引:

CREATE INDEX ord_customer_ix ON orders (customer_id);

在上述语句中,customer_id 列是索引键,索引本身被命名为 ord_customer_ix

注意:主键和唯一键会自动创建索引,但可能需要为外键创建索引。

另请参见:

  • “Data Integrity”(数据完整性);
  • 《Oracle Database SQL Language Reference》(《Oracle 数据库 SQL 语言参考》),了解 CREATE INDEX 语句的语法和语义。
Composite Indexes(复合索引)

复合索引(也称串联索引)是基于表中多个列的索引。

创建复合索引时,需按对查询最合理的顺序排列列(列在表中无需相邻)。

对于 WHERE 子句引用复合索引全部列或前导列的 SELECT 语句,复合索引可加快数据检索速度,因此定义索引时的列顺序至关重要。通常,应将访问频率最高的列放在前面。

例如,假设应用程序频繁查询 employees 表的 last_namejob_idsalary 列,且 last_name 的基数较高(即不同值的数量占表行数的比例较大),可按以下列顺序创建索引:

CREATE INDEX employees_ix ON employees (last_name, job_id, salary);

以下查询可使用该索引:

  • 同时访问三列的查询;
  • 仅访问 last_name 列的查询;
  • 仅访问 last_namejob_id 列的查询。

而不访问 last_name 列的查询无法使用该索引。

注意:在某些情况下(如前导列基数极低时),数据库可能对该索引执行跳跃扫描(参见“Index Skip Scan”(索引跳跃扫描))。

满足以下任一条件时,同一表上可存在列顺序相同的多个索引:

  1. 索引类型不同(例如,可在同一列上创建位图索引和 B 树索引);
  2. 分区方案不同(例如,可创建本地分区索引和全局分区索引);
  3. 唯一性不同(例如,可在同一组列上创建唯一索引和非唯一索引)。

例如,同一表的同一列(按相同顺序)可存在非分区索引、全局分区索引和本地分区索引,但同一时间仅能有一个相同列数、相同顺序的索引可见。

此特性支持在不删除现有索引的情况下迁移应用程序(无需重新创建具有不同属性的索引)。此外,在 OLTP 数据库中,若索引键值持续递增导致新条目集中插入同一组索引块(产生“热点”),可通过此特性将非分区索引升级为全局分区索引,以缓解热点问题。

若同一组列上的索引在类型或分区方案上无差异,则这些索引的列顺序必须不同。例如,以下 SQL 语句指定的列顺序是有效的:

CREATE INDEX employee_idx1 ON employees (last_name, job_id);
CREATE INDEX employee_idx2 ON employees (job_id, last_name);

另请参见:《Oracle Database SQL Tuning Guide》(《Oracle 数据库 SQL 调优指南》),了解如何在同一组列上创建多个索引。

Unique and Nonunique Indexes(唯一索引与非唯一索引)

索引分为唯一索引(Unique Indexes)和非唯一索引(Nonunique Indexes):

  • 唯一索引:保证表中没有两行在键列(或多列)上具有重复值。例如,应用程序可能要求所有员工的员工 ID 不重复。在唯一索引中,每个数据值对应一个行标识(rowid),叶块中的数据仅按键排序。
  • 非唯一索引:允许索引列(或多列)中存在重复值。例如,employees 表的 first_name 列可能包含多个“Mike”。非唯一索引会将 rowid 按排序顺序包含在键中,因此非唯一索引按索引键和 rowid(升序)排序。

除位图索引或簇键列值为 NULL 的情况外,Oracle 数据库不会为所有键列均为 NULL 的表行创建索引。

Types of Indexes(索引类型)

Oracle 数据库提供多种索引方案,可提供互补的性能功能。

B 树索引是标准索引类型,适用于高选择性索引(每个索引条目对应少量行)和主键索引;作为复合索引时,B 树索引可按索引列排序检索数据。B 树索引包含下表所示的子类型:

B 树索引子类型(B-Tree Index Subtype) 描述(Description) 参考文档(To Learn More)
索引组织表(Index-organized tables) 索引组织表与堆组织表不同,因为其数据本身就是索引 “Overview of Index-Organized Tables”(索引组织表概述)
反向键索引(Reverse key indexes) 此类索引会反转索引键的字节顺序(例如,103 存储为 301),可将索引插入操作分散到多个块中 “Reverse Key Indexes”(反向键索引)
降序索引(Descending indexes) 此类索引按降序存储特定列(或多列)的数据 “Ascending and Descending Indexes”(升序与降序索引)
B 树簇索引(B-tree cluster indexes) 此类索引按降序存储特定列(或多列)的数据 “Ascending and Descending Indexes”(升序与降序索引)

以下表格列出不使用 B 树结构的索引类型:

类型(Type) 描述(Description) 参考文档(To Learn More)
位图索引和位图连接索引(Bitmap and bitmap join indexes) 位图索引的每个索引条目使用位图指向多个行(B 树索引条目指向单行);位图连接索引是用于两个或多个表连接的位图索引 “Overview of Bitmap Indexes”(位图索引概述)
基于函数的索引(Function-based indexes) 此类索引包含经函数(如 UPPER 函数)转换的列,或包含在表达式中的列;B 树索引或位图索引均可为基于函数的索引 “Overview of Function-Based Indexes”(基于函数的索引概述)
应用程序域索引(Application domain indexes) 用户为应用程序特定域中的数据创建的索引;物理索引无需使用传统索引结构,可存储在 Oracle 数据库(作为表)或外部(作为文件) “Overview of Application Domain Indexes”(应用程序域索引概述)

另请参见:

  • 《Oracle Database Administrator’s Guide》(《Oracle 数据库管理员指南》),了解如何管理索引;
  • 《Oracle Database SQL Tuning Guide》(《Oracle 数据库 SQL 调优指南》),了解不同的索引访问路径。
How the Database Maintains Indexes(数据库如何维护索引)

索引创建后,数据库会自动维护和使用索引。

索引会自动反映其底层表的数据变化(如添加、更新和删除行),无需用户干预。

即使插入新行,索引数据的检索性能也几乎保持稳定。但表上的索引过多会降低 DML 性能,因为数据库需同步更新所有相关索引。

另请参见:

  • 《Oracle Database Administrator’s Guide》(《Oracle 数据库管理员指南》),了解更多关于自动索引的信息;
  • 《Oracle Database Licensing Information User Manual》(《Oracle 数据库许可信息用户手册》),了解不同版本和服务支持的功能详情。
Index Storage(索引存储)

Oracle 数据库将索引数据存储在索引段中。

数据块中可用于索引数据的空间 = 数据块大小 - 块开销 - 条目开销 - rowid - 每个索引值的 1 个长度字节。

索引段的表空间要么是所有者的默认表空间,要么是 CREATE INDEX 语句中指定的表空间。为便于管理,可将索引存储在与表不同的表空间中。例如,可选择不备份仅包含索引的表空间(索引可重建),从而减少备份所需的时间和存储。

另请参见:“Overview of Index Blocks”(索引块概述),了解索引块的类型(根块、分支块和叶块)以及索引条目在块中的存储方式。

Overview of B-Tree Indexes(B 树索引概述)

B 树(Balanced Trees,平衡树的简称)是最常见的数据库索引类型。B 树索引是按范围划分的有序值列表,通过将键与单行或行范围关联,B 树索引可为多种查询(包括精确匹配查询和范围查询)提供出色的检索性能。

下图展示 B 树索引的结构,示例为基于 employees 表中外键列 department_id 的索引:

Figure 5-1 Internal Structure of a B-tree Index)

B 树索引概述包含的主题

  • Branch Blocks and Leaf Blocks(分支块与叶块)
  • Index Scans(索引扫描)
  • Reverse Key Indexes(反向键索引)
  • Ascending and Descending Indexes(升序与降序索引)
  • Index Compression(索引压缩)

Branch Blocks and Leaf Blocks(分支块与叶块)

B 树索引包含两种类型的块:用于查找的分支块(Branch Block)和用于存储键值的叶块(Leaf Block)。B 树索引的上层分支块包含指向下层索引块的索引数据。

在图 5-1 中,根分支块包含条目“0-40”,指向次分支层最左侧的块;该分支块包含“0-10”“11-19”等条目,每个条目指向包含对应范围键值的叶块。

B 树索引具有平衡性,所有叶块始终处于同一深度,因此从索引的任意位置检索记录所需时间大致相同。索引的高度(Height)是从根块到叶块所需经过的块数,分支级别(Branch Level)= 高度 - 1。在图 5-1 中,索引高度为 3,分支级别为 2。

分支块存储进行分支决策所需的最小键前缀,此方式可在每个分支块中存储尽可能多的数据。分支块包含指向子块(包含对应键)的指针,键和指针的数量受块大小限制。

叶块包含所有已索引的数据值及用于定位实际行的 rowid,每个条目按“键 + rowid”排序。在叶块内部,键和 rowid 与其左右相邻条目链接;叶块之间也采用双向链接。在图 5-1 中,最左侧的叶块(0-10)与第二个叶块(11-19)链接。

注意:基于字符数据列的索引,其排序基于数据库字符集中字符的二进制值。

Index Scans(索引扫描)

在索引扫描中,数据库通过遍历索引(使用语句指定的已索引列值)检索行。若数据库为查找某个值而扫描索引,只需 n 次 I/O 即可找到该值(n 为 B 树索引的高度),这是 Oracle 数据库索引的基本原理。

若 SQL 语句仅访问已索引列,数据库会直接从索引读取值,无需访问表;若语句同时访问已索引列和未索引列,数据库会使用 rowid 查找表中的行。通常,数据库会交替读取索引块和表块以检索表数据。

Full Index Scan(全索引扫描)

全索引扫描按顺序读取整个索引,当 SQL 语句的谓词(WHERE 子句)引用索引中的列,或在某些无谓词的场景下,可执行全索引扫描。由于数据按索引键排序,全扫描可避免排序操作。

** Example 5-1 Full Index Scan(示例 5-1:全索引扫描)**
假设应用程序执行以下查询:

SELECT department_id, last_name, salary FROM employees
WHERE salary > 5000 ORDER BY department_id, last_name;

在此示例中,department_idlast_namesalary 是复合索引键。Oracle 数据库对该索引执行全扫描,按排序顺序(按部门 ID 和姓氏)读取索引,并过滤 salary 属性。通过这种方式,数据库扫描的数据量小于 employees 表(表包含比查询更多的列),且无需对数据进行排序。

全扫描读取的索引条目可能如下:

50,Atkinson,2800,rowid
60,Austin,4800,rowid
70,Baer,10000,rowid
80,Abel,11000,rowid
80,Ande,6400,rowid
110,Austin,7200,rowid
Fast Full Index Scan(快速全索引扫描)

快速全索引扫描是一种全索引扫描,数据库直接从索引访问数据(无需访问表),且读取索引块时无需按特定顺序。

满足以下两个条件时,快速全索引扫描可替代全表扫描:

  1. 索引必须包含查询所需的所有列;
  2. 包含全 NULL 值的行不得出现在查询结果集中。要保证这一点,索引中至少有一列需满足以下任一条件:
    • 具有 NOT NULL 约束;
    • 存在防止 NULL 值进入查询结果集的谓词。

Example 5-2 Fast Full Index Scan(示例 5-2:快速全索引扫描)
假设应用程序执行以下不含 ORDER BY 子句的查询:

SELECT last_name, salary FROM employees;

last_name 列具有 NOT NULL 约束,若 last_namesalary 是复合索引键,则快速全索引扫描可通过读取索引条目获取所需信息,示例如下:

Baida,2900,rowid
Atkinson,2800,rowid
Zlotkey,10500,rowid
Austin,7200,rowid
Austin,4800,rowid
Baer,10000,rowid
Index Range Scan(索引范围扫描)

索引范围扫描是对索引的有序扫描,需满足两个条件:索引的一个或多个前导列在条件中指定;索引键可能对应 0、1 或多个值。

条件(Condition)是由一个或多个表达式和逻辑(布尔)运算符组成的组合,返回 TRUEFALSEUNKNOWN

数据库通常使用索引范围扫描访问选择性数据。选择性(Selectivity)是查询选择的表行数占总行数的百分比(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 列,且每个索引键可能对应多个 rowid(例如,有两名员工名为 Austin,对应两个 rowid),数据库可执行范围扫描。

索引范围扫描的范围可双向受限(如查询部门 ID 在 10 到 40 之间的部门),也可单向受限(如查询部门 ID 大于 40 的部门)。扫描索引时,数据库会在叶块中向前或向后移动。例如,扫描部门 ID 在 10 到 40 之间的记录时,数据库会先定位包含最小键值(≥10)的第一个索引叶块,然后沿叶节点链表横向扫描,直至找到大于 40 的值。

Index Unique Scan(索引唯一扫描)

与索引范围扫描不同,索引唯一扫描的每个索引键最多对应 0 或 1 个 rowid。

当谓词使用等值运算符引用唯一索引的所有键列时,数据库会执行唯一扫描。由于不存在第二个匹配记录,索引唯一扫描找到第一条记录后即停止处理。

例如,用户执行以下查询:

SELECT * FROM employees WHERE employee_id = 5;

假设 employee_id 列是主键且已索引,索引条目如下:

1,rowid
2,rowid
4,rowid
5,rowid
6,rowid

此时,数据库可通过索引唯一扫描定位员工 ID 为 5 的行的 rowid。

Index Skip Scan(索引跳跃扫描)

索引跳跃扫描使用复合索引的逻辑子索引,数据库“跳跃”遍历单个索引,如同搜索多个独立索引。

若复合索引前导列的不同值较少,且非前导键的不同值较多,跳跃扫描可带来优势。当查询谓词未指定复合索引的前导列时,数据库可能选择索引跳跃扫描。

Example 5-3 Skip Scan of a Composite Index(示例 5-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.example.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';

另请参见:《Oracle Database SQL Tuning Guide》(《Oracle 数据库 SQL 调优指南》),了解更多关于跳跃扫描的信息。

Index Clustering Factor(索引聚簇因子)

索引聚簇因子用于衡量行的顺序与已索引值(如员工姓氏)的关联程度,顺序性越高,聚簇因子越低。

聚簇因子可粗略衡量通过索引读取整个表所需的 I/O 数量:

  • 若聚簇因子较高,在大型索引范围扫描中,Oracle 数据库需执行相对较多的 I/O。索引条目指向随机的表块,因此数据库可能需反复读取同一块以获取索引指向的数据。
  • 若聚簇因子较低,在大型索引范围扫描中,Oracle 数据库需执行相对较少的 I/O。范围内的索引键往往指向同一数据块,因此数据库无需反复读取同一块。

聚簇因子对索引扫描的意义在于:

  1. 指示数据库是否会对大型范围扫描使用索引;
  2. 指示表数据相对于索引键的组织程度;
  3. 指示若需按索引键排序行,是否应考虑使用索引组织表、分区或表簇。

Example 5-4 Clustering Factor(示例 5-4:聚簇因子)
假设 employees 表可存储在两个数据块中,表 5-3 展示了这两个块中的行(省略部分数据):

数据块 1(Data Block 1) 数据块 2(Data Block 2)
100 Steven King SKING …
156 Janette King JKING …
. 115 Alexander Khoo AKHOO …
. 149 Eleni Zlotkey EZLOTKEY …
. 200 Jennifer Whalen JWHALEN …
116 Shelli Baida SBAIDA … .
204 Hermann Baer HBAER … .
105 David Austin DAUSTIN … .
130 Mozhe Atkinson MATKINSO … 137 Renske Ladwig RLADWIG …
166 Sundar Ande SANDE … 173 Sundita Kumar SKUMAR …
174 Ellen Abel EABEL … 101 Neena Kochar NKOCHHAR …

行在块中按姓氏(粗体显示)排序。例如,数据块 1 最下方的行对应“Abel”,向上依次为“Ande”,按字母顺序排列,直至块 1 最上方的“Steven King”;数据块 2 最下方的行对应“Kochar”,向上依次为“Kumar”,按字母顺序排列,直至块 2 最上方的“Zlotkey”。

假设 last_name 列上存在索引,每个姓名条目对应一个 rowid,索引条目逻辑上如下:

Abel,block1row1
Ande,block1row2
Atkinson,block1row3
Austin,block1row4
Baer,block1row5

另假设 employee_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 的聚簇因子较高,表明单个叶块中的相邻索引条目指向同一数据块的可能性较低。

另请参见:《Oracle Database Reference》(《Oracle 数据库参考》),了解 ALL_INDEXES 视图。

Reverse Key Indexes(反向键索引)

反向键索引是 B 树索引的一种类型,会物理反转每个索引键的字节顺序,但保持列顺序不变。

例如,若索引键为 20,在标准 B 树索引中存储的两个十六进制字节为 C1,15,则在反向键索引中存储为 15,C1

反转键可解决 B 树索引右侧叶块的竞争问题,在 Oracle 实时应用集群(Oracle RAC)数据库中(多个实例反复修改同一块),此问题可能尤为严重。例如,orders 表的订单主键为连续值,集群中的一个实例添加订单 20,另一个实例添加订单 21,每个实例都会将键写入索引右侧的同一叶块。

在反向键索引中,字节顺序的反转会将连续键的插入分散到索引的所有叶块中。例如,在标准键索引中相邻的键(如 20 和 21),在反向键索引中会存储在相距较远的独立块中,从而使连续键插入的 I/O 分布更均匀。

由于索引中的数据存储时未按列键排序,反向键结构在某些情况下会无法执行索引范围扫描查询。例如,用户查询订单 ID 大于 20 的记录时,数据库无法从包含该 ID 的块开始,沿叶块横向扫描。

Ascending and Descending Indexes(升序与降序索引)

在升序索引中,Oracle 数据库按升序存储数据。默认情况下:

  • 字符数据按每个字节包含的二进制值排序;
  • 数值数据按从小到大排序;
  • 日期数据按从早到晚排序。

以下示例为升序索引的创建语句:

CREATE INDEX emp_deptid_ix ON hr.employees(department_id);

Oracle 数据库会按 department_id 列对 hr.employees 表排序,并将 department_id 及对应 rowid 按升序(从 0 开始)加载到升序索引中。使用该索引时,Oracle 数据库会搜索排序后的 department_id 值,并通过关联的 rowid 定位具有目标 department_id 值的行。

通过在 CREATE INDEX 语句中指定 DESC 关键字,可创建降序索引,此时索引按降序存储指定列(或多列)的数据。若表 5-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 Database SQL Tuning Guide》(《Oracle 数据库 SQL 调优指南》),了解更多关于升序和降序索引搜索的信息;
  • 《Oracle Database SQL Language Reference》(《Oracle 数据库 SQL 语言参考》),了解 CREATE INDEX 语句的 ASCDESC 选项。

Index Compression(索引压缩)

Oracle 数据库可采用不同的压缩算法减少索引占用的空间。

Prefix Compression(前缀压缩)

Oracle 数据库可使用前缀压缩(也称键压缩)压缩 B 树索引或索引组织表中主键列值的部分内容,大幅减少索引占用的空间。

未压缩的索引条目为一个整体;使用前缀压缩的索引条目分为两部分:

  • 前缀条目(Prefix Entry):分组部分;
  • 后缀条目(Suffix Entry):唯一或接近唯一的部分。

数据库通过在索引块中共享前缀条目实现压缩。

注意:若键未定义唯一部分,数据库会通过在分组部分后附加 rowid 来生成唯一部分。

默认情况下:

  • 唯一索引的前缀包含除最后一列外的所有键列;
  • 非唯一索引的前缀包含所有键列。

例如,在 oe.orders 表的两列上创建复合唯一索引:

CREATE UNIQUE INDEX orders_mod_stat_ix ON orders ( order_mode, order_status );

上述示例中,索引键可能为“online,0”,rowid 存储在条目的键数据部分,不属于键本身。

注意:若在单个列上创建唯一索引,Oracle 数据库无法使用前缀键压缩,因为不存在公共前缀。

另假设在相同列上创建非唯一索引:

CREATE INDEX orders_mod_stat_ix ON orders ( order_mode, order_status );

order_modeorder_status 列中存在重复值,某索引块中的条目可能如下:

online,0,AAAPvCAAFAAAAFaAAa
online,0,AAAPvCAAFAAAAFaAAg
online,0,AAAPvCAAFAAAAFaAAl
online,2,AAAPvCAAFAAAAFaAAm
online,3,AAAPvCAAFAAAAFaAAq
online,3,AAAPvCAAFAAAAFaAAt

上述示例中,键前缀为 order_modeorder_status 值的组合(如“online,0”),后缀为 rowid(如“AAAPvCAAFAAAAFaAAa”)。由于 rowid 在数据库中唯一,因此整个索引条目可通过 rowid 实现唯一。

若上述示例中的索引使用默认前缀压缩(通过 COMPRESS 关键字指定),则“online,0”“online,3”等重复键前缀会被压缩,数据库实现压缩的逻辑如下:

online,0  AAAPvCAAFAAAAFaAAa
          AAAPvCAAFAAAAFaAAg
          AAAPvCAAFAAAAFaAAl
online,2  AAAPvCAAFAAAAFaAAm
online,3  AAAPvCAAFAAAAFaAAq
          AAAPvCAAFAAAAFaAAt

后缀条目(rowid)构成索引行的压缩版本,每个后缀条目引用与其存储在同一索引块中的前缀条目。

此外,创建使用前缀压缩的索引时,可指定前缀长度。例如,指定 COMPRESS 1,则前缀为 order_mode,后缀为 order_status,rowid。对于上述索引块中的值,索引会提取重复的前缀“online”,逻辑上表示如下:

online  0,AAAPvCAAFAAAAFaAAa
        0,AAAPvCAAFAAAAFaAAg
        0,AAAPvCAAFAAAAFaAAl
        2,AAAPvCAAFAAAAFaAAm
        3,AAAPvCAAFAAAAFaAAq
        3,AAAPvCAAFAAAAFaAAt

索引在每个叶块中最多存储一个特定前缀,仅 B 树索引的叶块中的键会被压缩,分支块中的键后缀可能被截断,但不会被压缩。

另请参见:

  • 《Oracle Database Administrator’s Guide》(《Oracle 数据库管理员指南》),了解如何使用压缩索引;
  • 《Oracle Database VLDB and Partitioning Guide》(《Oracle 数据库 VLDB 和分区指南》),了解如何对分区索引使用前缀压缩;
  • 《Oracle Database SQL Language Reference》(《Oracle 数据库 SQL 语言参考》),了解 CREATE INDEX 语句的 key_compression 子句。
Advanced Index Compression(高级索引压缩)

从 Oracle 数据库 12c 第 1 版(12.1.0.2)开始,高级索引压缩对堆组织表上的受支持索引进行了优化,改进了传统前缀压缩。

Benefits of Advanced Index Compression(高级索引压缩的优势)
前缀压缩在支持的索引类型、压缩比和易用性方面存在局限性。与前缀压缩(对每个块使用固定的重复键消除)不同,高级索引压缩在每个块上使用自适应重复键消除,主要优势如下:

  1. 数据库会自动为每个块选择最佳压缩方式,使用多种内部算法(如列内级前缀、重复键消除和 rowid 压缩),无需用户了解数据特征;
  2. 高级压缩适用于非唯一索引和唯一索引,而前缀压缩仅对部分非唯一索引效果较好,对前导列重复较少的索引压缩比较低;
  3. 压缩后的索引使用方式与未压缩索引相同,支持相同的访问路径(唯一键查找、范围扫描和快速全扫描);
  4. 索引可从父表或包含它的表空间继承高级压缩属性。

How Advanced Index Compression Works(高级索引压缩的工作原理)
高级索引压缩在块级别工作,为每个块提供最佳压缩效果,具体流程如下:

  1. 创建索引时,当叶块满时,数据库会自动将块压缩到最佳级别;
  2. 因 DML 操作重组索引块时,若数据库可通过压缩为新索引条目腾出足够空间,则不会发生块拆分;而在不使用高级索引压缩的情况下,块满时必然发生块拆分。

Advanced Index Compression HIGH(高级索引压缩 HIGH 级别)
在 Oracle 数据库 12c 第 2 版(12.2)之前,高级索引压缩仅支持低压缩(COMPRESS ADVANCED LOW);现在还可指定高压缩(COMPRESS ADVANCED HIGH,默认级别)。高级索引压缩 HIGH 级别具有以下优势:

  1. 在大多数情况下实现更高的压缩比,同时提升访问该索引的查询性能;
  2. 采用比高级低压缩更复杂的压缩算法;
  3. 将数据存储在压缩单元(一种特殊的磁盘格式)中。

注意:使用 HIGH 压缩时,所有块都会被压缩;使用 LOW 压缩时,数据库可能会保留部分未压缩的块,可通过统计信息确定未压缩块的数量。

Example 5-5 Creating an Index with Advanced High Compression(示例 5-5:创建使用高级高压缩的索引)
以下示例为 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

另请参见:

  • 《Oracle Database Administrator’s Guide》(《Oracle 数据库管理员指南》),了解如何启用高级索引压缩;
  • 《Oracle Database SQL Language Reference》(《Oracle 数据库 SQL 语言参考》),了解 CREATE INDEX 语句的 key_compression 子句;
  • 《Oracle Database Reference》(《Oracle 数据库参考》),了解 ALL_INDEXES 视图。

Overview of Bitmap Indexes(位图索引概述)

在位图索引中,数据库为每个索引键存储一个位图。在传统 B 树索引中,一个索引条目指向单行;而在位图索引中,每个索引键存储指向多行的指针。

位图索引主要设计用于数据仓库环境,或查询频繁以临时方式引用多个列的环境。以下场景可考虑使用位图索引:

  1. 索引列的基数较低(即不同值的数量与表行数相比占比较小);
  2. 索引表为只读表,或不会因 DML 语句发生大量修改。

以数据仓库为例,sh.customers 表的 cust_gender 列仅有两个可能值(M 和 F),若频繁查询特定性别的客户数量,customers.cust_gender 列适合创建位图索引。

位图中的每个位对应一个可能的 rowid,若位被置为 1,则对应 rowid 的行包含该键值。映射函数会将位位置转换为实际 rowid,因此尽管位图索引的内部表示不同,但提供的功能与 B 树索引相同。

若更新单行中的索引列,数据库会锁定索引键条目(如 M 或 F),而非锁定与更新行对应的单个位。由于一个键指向多行,对索引数据执行 DML 操作通常会锁定所有这些行,因此位图索引不适用于多数 OLTP 应用程序。

Example: Bitmap Indexes on a Single Table(示例:单个表上的位图索引)
本示例中,sh.customers 表的部分列适合创建位图索引。

Bitmap Join Indexes(位图连接索引)
位图连接索引是用于两个或多个表连接的位图索引。

Bitmap Storage Structure(位图存储结构)
Oracle 数据库使用 B 树索引结构存储每个索引键的位图。

另请参见:

  • 《Oracle Database SQL Tuning Guide》(《Oracle 数据库 SQL 调优指南》),了解更多关于位图索引的信息;
  • 《Oracle Database Data Warehousing Guide》(《Oracle 数据库数据仓库指南》),了解如何在数据仓库中使用位图索引。

Example: Bitmap Indexes on a Single Table(示例:单个表上的位图索引)

本示例中,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        M
         2 Koch       F        F
         3 Emmerson   M        M
         4 Hardy      M        M
         5 Gowen      single   M
         6 Charles    F        F
         7 Ingram     single   F

7 rows selected.

cust_marital_statuscust_gender 列的基数较低,而 cust_idcust_last_name 列的基数较高,因此 cust_marital_statuscust_gender 列适合创建位图索引,其他列则不适合。对于基数较高的列,创建唯一 B 树索引通常能提供更高效的存储和检索方式。

表 5-4 展示了针对上述示例中 cust_gender 列的位图索引,包含两个独立的位图(分别对应两种性别):

值(Value) 行 1(Row 1) 行 2(Row 2) 行 3(Row 3) 行 4(Row 4) 行 5(Row 5) 行 6(Row 6) 行 7(Row 7)
M 1 0 1 1 1 0 0
F 0 1 0 0 0 1 1

映射函数会将位图中的每个位转换为 customers 表的 rowid,位值由表中对应行的值决定。例如,值为 M 的位图的第一位为 1,因为 customers 表第一行的性别为 M;值为 M 的位图在第 2、6、7 行的位为 0,因为这些行的性别不是 M。

注意:与 B 树索引不同,位图索引可包含全部键列为 NULL 值的键。对某些 SQL 语句(如包含聚合函数 COUNT 的查询)而言,索引 NULL 值可能很有用。

若分析师需调查客户的人口统计趋势,例如“女性客户中有多少是单身或离异状态?”,对应的 SQL 查询如下:

SELECT COUNT(*) 
FROM customers 
WHERE cust_gender = 'F' AND cust_marital_status IN ('single', 'divorced');

位图索引可通过统计结果位图中 1 的数量来高效处理此查询(如表 5-5 所示)。为定位满足条件的客户,Oracle 数据库可使用结果位图访问表。

表 5-5 展示了针对两列的示例位图:

值(Value) 行 1(Row 1) 行 2(Row 2) 行 3(Row 3) 行 4(Row 4) 行 5(Row 5) 行 6(Row 6) 行 7(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 子句中多个条件对应的索引,在访问表之前过滤掉仅满足部分条件的行,这种方式通常能显著提升响应速度。

Bitmap Join Indexes(位图连接索引)

位图连接索引是用于两个或多个表连接的位图索引。

对于表列中的每个值,该索引存储索引表中对应行的 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

Figure 5-2 Bitmap Join Index

从逻辑上看,employees_bm_idx 是以下 SQL 查询中 jobs.title 列的索引(含示例输出)。索引中的 job_title 键指向 employees 表中的行,查询会计师数量时,可直接使用该索引,无需访问 employeesjobs 表,因为索引本身已包含所需信息:

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          AAAQNKAAFAAAABSAAM
Accountant          AAAQNKAAFAAAABSAAJ
Accountant          AAAQNKAAFAAAABSAAL
Accountant          AAAQNKAAFAAAABSAAN
Accountant          AAAQNKAAFAAAABSAAK
Accounting Manager  AAAQNKAAFAAAABTAAH
Administration Assistant AAAQNKAAFAAAABTAAC
Administration Vice President AAAQNKAAFAAAABSAAC
Administration Vice President AAAQNKAAFAAAABSAAB

在数据仓库中,连接条件通常是维度表主键列与事实表外键列之间的等值连接。与物化连接视图(提前物化连接的另一种方案)相比,位图连接索引在存储方面往往更高效。

另请参见:《Oracle Database Data Warehousing Guide》(《Oracle 数据库数据仓库指南》),了解更多关于位图连接索引的信息。

Bitmap Storage Structure(位图存储结构)

Oracle 数据库使用 B 树索引结构存储每个索引键的位图。

例如,若 jobs.job_title 是位图索引的键列,则会有一个 B 树存储索引数据,叶块中存储各个位图。

Example 5-6 Bitmap Storage Example(示例 5-6:位图存储示例)
假设 jobs.job_title 列包含唯一值“Shipping Clerk”“Stock Clerk”等,该位图索引的条目包含以下组件:

  1. 索引键(职位名称);
  2. rowid 范围的起始 rowid(low rowid)和结束 rowid(high rowid);
  3. 该范围内特定 rowid 对应的位图。

从逻辑上看,该索引的叶块条目可能如下:

Shipping Clerk,AAAPzRAAFAAAABSABQ,AAAPzRAAFAAAABSABZ,0010000100
Shipping Clerk,AAAPzRAAFAAAABSABa,AAAPzRAAFAAAABSABh,010010
Stock Clerk,AAAPzRAAFAAAABSAAa,AAAPzRAAFAAAABSAAc,1001001100
Stock Clerk,AAAPzRAAFAAAABSAAd,AAAPzRAAFAAAABSAAt,0101001001
Stock Clerk,AAAPzRAAFAAAABSAAu,AAAPzRAAFAAAABSABz,100001

由于 rowid 范围不同,同一职位名称可能出现在多个条目中。

若某会话将一名员工的职位 ID 从“Shipping Clerk”更新为“Stock Clerk”,则该会话需对旧值(Shipping Clerk)和新值(Stock Clerk)的索引键条目拥有排他访问权。Oracle 数据库会锁定这两个条目指向的行(而非“Accountant”或其他键指向的行),直至 UPDATE 操作提交。

位图索引的数据存储在一个段中,Oracle 数据库将每个位图分为一个或多个部分,每个部分占用单个数据块的一部分。

另请参见:“User Segments”(用户段),了解不同类型的段及其创建方式。

Overview of Function-Based Indexes(基于函数的索引概述)

基于函数的索引会计算包含一个或多个列的函数或表达式的值,并将其存储在索引中。基于函数的索引既可以是 B 树索引,也可以是位图索引。

索引函数可以是算术表达式,也可以是包含 SQL 函数、用户定义 PL/SQL 函数、包函数或 C 调用的表达式。例如,某函数可计算两列值的和。

Uses of Function-Based Indexes(基于函数的索引的用途)
对于 WHERE 子句中包含函数的语句,基于函数的索引能高效地执行查询评估。仅当查询中包含该函数时,数据库才会使用基于函数的索引;但在处理 INSERTUPDATE 语句时,数据库仍需评估该函数以执行操作。

Optimization with Function-Based Indexes(基于函数的索引的优化)
对于 WHERE 子句中包含表达式的查询,优化器可对基于函数的索引执行索引范围扫描。

另请参见:

  • 《Oracle Database Administrator’s Guide》(《Oracle 数据库管理员指南》),了解如何创建基于函数的索引;
  • 《Oracle Database Development Guide》(《Oracle 数据库开发指南》),了解更多关于基于函数的索引的信息;
  • 《Oracle Database SQL Language Reference》(《Oracle 数据库 SQL 语言参考》),了解基于函数的索引的限制和使用说明。

Uses of Function-Based Indexes(基于函数的索引的用途)

对于 WHERE 子句中包含函数的语句,基于函数的索引能高效地执行查询评估。仅当查询中包含该函数时,数据库才会使用基于函数的索引;但在处理 INSERTUPDATE 语句时,数据库仍需评估该函数以执行操作。

Example 5-7 Index Based on Arithmetic Expression(示例 5-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                    Alyssa                  27000
        175 Hutton                   Louise                  26400
        149 Zlotkey                  Eleni                   25200
        169 Bloom                    Harrison                24000

Example 5-8 Index Based on an UPPER Function(示例 5-8:基于 UPPER 函数的索引)
基于 SQL 函数 UPPER(column_name)LOWER(column_name) 的函数索引可支持不区分大小写的搜索。例如,若 employees 表的 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';

Example 5-9 Indexing Specific Rows in a Table(示例 5-9:索引表中的特定行)
基于函数的索引还可用于仅索引表中的特定行。例如,sh.customers 表的 cust_valid 列值为“I”或“A”,若仅需索引值为“A”的行,可定义一个函数(对非“A”行返回 NULL 值),并创建如下索引:

CREATE INDEX cust_valid_idx ON customers ( CASE cust_valid WHEN 'A' THEN 'A' END );

另请参见:

  • 《Oracle Database Globalization Support Guide》(《Oracle 数据库全球化支持指南》),了解语言索引的相关信息;
  • 《Oracle Database SQL Language Reference》(《Oracle 数据库 SQL 语言参考》),了解更多关于 SQL 函数的信息。

Optimization with Function-Based Indexes(基于函数的索引的优化)

对于 WHERE 子句中包含表达式的查询,优化器可对基于函数的索引执行索引范围扫描。

当谓词的选择性较高(即仅选择较少行)时,范围扫描访问路径的优势尤为明显。在示例 5-7 中,若基于表达式 12*salary*commission_pct 创建索引,优化器可执行索引范围扫描。

虚拟列也可用于加速对表达式派生数据的访问。例如,可将虚拟列 annual_sal 定义为 12*salary*commission_pct,并在该列上创建基于函数的索引。

优化器通过解析 SQL 语句中的表达式,再比较语句与基于函数的索引的表达式树,实现表达式匹配。此比较不区分大小写,且忽略空格。

另请参见:

  • “Overview of the Optimizer”(优化器概述);
  • 《Oracle Database SQL Tuning Guide》(《Oracle 数据库 SQL 调优指南》),了解更多关于收集统计信息的内容;
  • 《Oracle Database Administrator’s Guide》(《Oracle 数据库管理员指南》),了解如何向表添加虚拟列。

Overview of Application Domain Indexes(应用程序域索引概述)

应用程序域索引是特定于应用程序的自定义索引。

扩展索引功能可实现以下目标:

  1. 支持对自定义复杂数据类型(如文档、空间数据、图像和视频片段)创建索引;
  2. 利用专用索引技术。

可将应用程序特定的索引管理例程封装为 indextype 模式对象,然后在表列或对象类型的属性上定义域索引。扩展索引能高效地处理应用程序特定的运算符。

应用程序软件(称为“插件”)控制域索引的结构和内容,数据库与应用程序交互以构建、维护和搜索域索引。索引结构本身可存储在数据库中(作为索引组织表)或外部(作为文件)。

另请参见:

  • 《Oracle Database Development Guide》(《Oracle 数据库开发指南》),了解更多关于复杂数据类型的信息;
  • 《Oracle Database Data Cartridge Developer’s Guide》(《Oracle 数据库数据插件开发指南》),了解如何在 Oracle 数据库扩展架构中使用数据插件。

Overview of Index-Organized Tables(索引组织表概述)

索引组织表是存储在 B 树索引结构变体中的表;与之不同的是,堆组织表会将行插入到合适的位置。

在索引组织表中,行存储在基于表主键的索引中,B 树中的每个索引条目还存储非键列值。因此,索引即数据,数据即索引。应用程序可像操作堆组织表一样,通过 SQL 语句操作索引组织表。

举个类比案例:假设人力资源经理有一架子纸板箱,每个箱子标有数字(1、2、3、4 等),但箱子并未按顺序放在架子上,而是每个箱子包含指向下一个顺序箱子的架子位置指针。

每个箱子中存储装有员工记录的文件夹,文件夹按员工 ID 排序。员工 King 的 ID 为 100(最小 ID),其文件夹在 1 号箱底部;员工 101 的文件夹在 100 上方,102 在 101 上方,依此类推,直至 1 号箱装满,下一个文件夹则放在 2 号箱底部。

在此类比中,按员工 ID 排序文件夹无需维护单独索引即可高效查找。例如,用户需要员工 107、120 和 122 的记录时,经理无需先搜索索引再单独检索文件夹,而是可按顺序搜索文件夹,找到后直接获取。

索引组织表通过主键或主键的有效前缀,能更快地访问表行。叶块中存储行的非键列,可避免额外的数据块 I/O。例如,员工 100 的薪资直接存储在索引行中。此外,由于行按主键顺序存储,通过主键或其前缀进行范围访问时,所需的块 I/O 最少。另一优势是无需为单独的主键索引占用空间。

当相关数据需存储在一起,或数据需按特定顺序物理存储时,索引组织表非常有用,典型用途包括信息检索、空间数据和 OLAP 应用程序。

索引组织表概述包含的主题

  • Index-Organized Table Characteristics(索引组织表的特征)
  • Index-Organized Tables with Row Overflow Area(带行溢出区域的索引组织表)
  • Secondary Indexes on Index-Organized Tables(索引组织表上的二级索引)

Index-Organized Table Characteristics(索引组织表的特征)

数据库系统通过操作 B 树索引结构,执行对索引组织表的所有操作。

下表总结了索引组织表与堆组织表的区别:

堆组织表(Heap-Organized Table) 索引组织表(Index-Organized Table)
rowid 唯一标识一行;可选择性定义主键约束 主键唯一标识一行;必须定义主键约束
ROWID 伪列中的物理 rowid 支持创建二级索引 ROWID 伪列中的逻辑 rowid 支持创建二级索引
可通过 rowid 直接访问单行 需通过主键间接访问单行
顺序全表扫描按某种顺序返回所有行 全索引扫描或快速全索引扫描按某种顺序返回所有行
可与其他表一起存储在表簇中 不可存储在表簇中
可包含 LONG 类型列和 LOB 类型列 可包含 LOB 列,但不可包含 LONG
可包含虚拟列(仅支持关系堆表) 不可包含虚拟列

索引组织表可通过在 CREATE TABLE 语句中添加 COMPRESS ADVANCED LOW 子句,使用高级低压缩(Advanced Low Compression)。高级低压缩是一种自适应前缀压缩,会自动计算每个叶块的最佳前缀列数,在块级别实现最优压缩,无需用户干预(包括在不适合压缩时不执行压缩的情况),可确保不会出现“负压缩”(压缩后空间反而增加),且能实现较高的压缩比,同时带来的总体开销可忽略不计。

若优化器判定查询可执行快速全扫描,索引组织表的扫描可卸载到 Exadata 智能扫描(Exadata Smart Scan),无需用户干预。若索引组织表叶块中包含所有关注的列,全单元格级处理(Full Cell Side Processing)将支持这些叶块;若部分列存储在溢出段中,智能扫描不会处理这些列,需由数据库完成扫描。

下图展示了索引组织的 departments 表的结构,叶块中存储表的行,按主键顺序排列。例如,第一个叶块中的第一个值为部门 ID 20、部门名称 Marketing、经理 ID 201、位置 ID 1800:

Figure 5-3 Index-Organized Table

Example 5-10 Scan of Index-Organized Table(示例 5-10:索引组织表的扫描)
索引组织表将所有数据存储在同一结构中,无需存储 rowid。如上图所示,索引组织表的叶块 1 中的条目可能如下(按主键排序):

20,Marketing,201,1800
30,Purchasing,114,1700

索引组织表的叶块 2 中的条目可能如下:

50,Shipping,121,1500
60,IT,103,1400

按主键顺序扫描索引组织表的行时,读取块的顺序如下:

  1. 块 1
  2. 块 2

Example 5-11 Scan of Heap-Organized Table(示例 5-11:堆组织表的扫描)
为对比堆组织表与索引组织表的数据访问方式,假设堆组织的 departments 表段的块 1 包含以下行:

50,Shipping,121,1500
20,Marketing,201,1800

块 2 包含以下行:

30,Purchasing,114,1700
60,IT,103,1400

该堆组织表的 B 树索引叶块包含以下条目(第一个值为主键,第二个为 rowid):

20,AAAPeXAAFAAAAAyAAD
30,AAAPeXAAFAAAAAyAAA
50,AAAPeXAAFAAAAAyAAC
60,AAAPeXAAFAAAAAyAAB

按主键顺序扫描表行时,读取表段块的顺序如下:

  1. 块 1
  2. 块 2
  3. 块 1
  4. 块 2

因此,本示例中堆组织表的块 I/O 数量是索引组织表示例的两倍。

另请参见:

  • “Table Organization”(表组织方式),了解更多关于堆组织表的内容;
  • “Introduction to Logical Storage Structures”(逻辑存储结构简介),了解段与数据块之间的关系。

Index-Organized Tables with Row Overflow Area(带行溢出区域的索引组织表)

创建索引组织表时,可指定单独的段作为行溢出区域。

在索引组织表中,B 树索引条目可能因包含整行而体积较大,因此需要单独的段存储这些条目;与之不同的是,B 树条目的体积通常较小(仅包含键和 rowid)。

若指定了行溢出区域,数据库可将索引组织表的行分为以下两部分:

  • 索引条目(Index Entry)
    包含所有主键列的值、指向行溢出部分的物理 rowid,以及(可选)部分非键列的值,存储在索引段中。

  • 溢出部分(Overflow Part)
    包含剩余非键列的值,存储在溢出存储区域段中。

另请参见:

  • 《Oracle Database Administrator’s Guide》(《Oracle 数据库管理员指南》),了解如何使用 CREATE TABLE 语句的 OVERFLOW 子句设置行溢出区域;
  • 《Oracle Database SQL Language Reference》(《Oracle 数据库 SQL 语言参考》),了解 CREATE TABLE ... OVERFLOW 语句的语法和语义。

Secondary Indexes on Index-Organized Tables(索引组织表上的二级索引)

二级索引是建立在索引组织表上的索引。

从某种意义上说,二级索引是“索引上的索引”,它是独立的模式对象,与索引组织表分开存储。

Oracle 数据库对索引组织表使用称为“逻辑 rowid”的行标识符,逻辑 rowid 是表主键的 Base64 编码表示,长度取决于主键长度。

由于插入操作,索引叶块中的行可能在块内或块间移动,但索引组织表中的行不会像堆组织表中的行那样迁移。由于索引组织表中的行没有永久物理地址,数据库使用基于主键的逻辑 rowid。

例如,假设 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 的部门名称时,可使用二级索引加快数据访问。

Logical Rowids and Physical Guesses(逻辑 rowid 与物理猜测)
二级索引使用逻辑 rowid 定位表行。

Bitmap Indexes on Index-Organized Tables(索引组织表上的位图索引)
索引组织表上的二级索引可以是位图索引,位图索引为每个索引键存储一个位图。

另请参见:

  • “Rowid Data Types”(Rowid 数据类型),了解 rowid 的使用及 ROWID 伪列;
  • “Chained and Migrated Rows”(链式行与迁移行),了解行迁移的原因及迁移导致 I/O 增加的原因;
  • 《Oracle Database Administrator’s Guide》(《Oracle 数据库管理员指南》),了解如何在索引组织表上创建二级索引;
  • 《Oracle Database VLDB and Partitioning Guide》(《Oracle 数据库 VLDB 和分区指南》),了解如何在索引组织表分区上创建二级索引。
Logical Rowids and Physical Guesses(逻辑 rowid 与物理猜测)

二级索引使用逻辑 rowid 定位表行。

逻辑 rowid 包含“物理猜测”(即索引条目首次创建时的物理 rowid),Oracle 数据库可使用物理猜测直接探测索引组织表的叶块,跳过主键搜索。即使行的物理位置发生变化,逻辑 rowid 依然有效(即使其包含的物理猜测已失效)。

对于堆组织表,通过二级索引访问数据需执行二级索引扫描,再额外执行一次 I/O 以获取包含该行的数据块;对于索引组织表,通过二级索引访问数据的方式取决于是否使用物理猜测及猜测的准确性:

  1. 无物理猜测时:需执行两次索引扫描(二级索引扫描,然后是主键索引扫描);
  2. 有物理猜测时:访问方式取决于猜测的准确性:
    • 物理猜测准确时:需执行二级索引扫描,再额外执行一次 I/O 以获取包含该行的数据块;
    • 物理猜测不准确时:需执行二级索引扫描,再执行一次 I/O 获取猜测指向的错误数据块,最后通过主键值对索引组织表执行索引唯一扫描。
Bitmap Indexes on Index-Organized Tables(索引组织表上的位图索引)

索引组织表上的二级索引可以是位图索引,位图索引为每个索引键存储一个位图。

当索引组织表上存在位图索引时,所有位图索引都会使用堆组织的映射表(Mapping Table)。映射表存储索引组织表的逻辑 rowid,每个映射表行存储对应索引组织表行的一个逻辑 rowid。

数据库通过搜索键访问位图索引,找到键后,将位图条目转换为物理 rowid:

  • 对于堆组织表,数据库使用物理 rowid 访问基表;
  • 对于索引组织表,数据库使用物理 rowid 访问映射表,再通过映射表获取的逻辑 rowid 访问索引组织表。

下图展示了对 departments_iot 表的查询的索引访问流程:

Figure 5-4 Bitmap Index on Index-Organized Table

注意:索引组织表中的行移动不会导致其上的位图索引失效。

另请参见:“Rowids of Row Pieces”(行片段的 Rowid),了解物理 rowid 与逻辑 rowid 的区别。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐