SQL调优指南笔记8:Optimizer Access Paths

article/2025/2/6 5:48:54

本文为SQL Tuning Guide第8章“优化访问路径”的笔记。

重要基本概念

  • access path
    The means by which the database retrieves data from a database. For example, a query using an index and a query using a full table scan use different access paths.
    数据库从数据库中检索数据的方法。 例如,使用索引的查询和使用全表扫描的查询使用不同的访问路径。
    访问路径是查询用来从行源检索行的一种技术。

  • heap-organized table
    A table in which the data rows are stored in no particular order on disk. By default, CREATE TABLE creates a heap-organized table.
    一个表,其中数据行在磁盘上没有特定存储顺序。 默认情况下,CREATE TABLE 创建一个堆组织表。

  • index-organized table
    A table whose storage organization is a variant of a primary B-tree index. Unlike a heap-organized table, data is stored in primary key order.
    存储组织是主 B 树索引的变体的表。 与堆组织表不同,数据按主键顺序存储。

  • external table
    A read-only table whose metadata is stored in the database but whose data in stored in files outside the database. The database uses the metadata describing external tables to expose their data as if they were relational tables.
    一个只读表,其元数据存储在数据库中,但其数据存储在数据库外部的文件中。 数据库使用描述外部表的元数据来展现它们的数据,就好像它们是关系表一样。

  • unselective
    A relatively large fraction of rows from a row set. A query becomes more unselective as the selectivity approaches 1. For example, a query that returns 999,999 rows from a table with one million rows is unselective. A query of the same table that returns one row is selective.
    行集中返回相对较大的一部分行。 随着选择性的接近1,查询变得更加不具选择性。例如,从一百万行的表中返回999,999行的查询是不具选择性的。 返回一行的同一个表的查询是具选择性的。

8.1 Introduction to Access Paths

行源是执行计划中的步骤返回的一组行。 行源可以是表、视图或联结或分组操作的结果。

诸如访问路径之类的一元操作是查询从行源检索行的一种技术,它接受单个行源作为输入。 例如,全表扫描是检索单个行源的行。 相比之下,联结是二元操作,从两个行源接收输入。

数据库对不同的关系数据结构使用不同的访问路径。 下表总结了主要数据结构的常见访问路径。
在这里插入图片描述

优化器考虑不同的可能执行计划,然后为每个计划分配一个成本。 优化器选择成本最低的计划。 一般来说,索引访问路径对于检索一小部分表行的语句更有效,而全表扫描在访问表的大部分数据时更有效。

8.2 Table Access Paths

表是 Oracle 数据库中数据组织的基本单位。

关系表是最常见的表类型。 关系表具有以下组织特征:

  • 堆组织表不以任何特定顺序存储行。
  • 索引组织表根据主键值对行进行排序。
  • 外部表是只读表,其元数据存储在数据库中,但其数据存储在数据库之外。

8.2.1 About Heap-Organized Table Access

默认情况下,表按堆组织,这意味着数据库将行放置在最适合的位置,而不是按照用户指定的顺序。

当用户添加行时,数据库将这些行放在数据段中的第一个可用空间中。 不保证按插入的顺序检索行。

8.2.1.1 Row Storage in Data Blocks and Segments: A Primer

数据库将行存储在数据块中。在表中,数据库可以在块底部的任何位置写入一行。 Oracle 数据库使用包含行目录和表目录的块开销来管理块本身。

一个extent由逻辑上连续的数据块组成。这些块在磁盘上可能不是物理上连续的。segment是一组extent,其中包含表空间内逻辑存储结构的所有数据。例如,Oracle 数据库分配一个或多个extent来形成表的数据segment。数据库还分配一个或多个extent以形成表的索引段。

默认情况下,数据库对永久的本地管理表空间使用自动段空间管理 (ASSM)。当会话首次将数据插入表时,数据库会格式化位图块。位图跟踪段中的块。数据库使用位图查找空闲块,然后在写入之前格式化每个块。 ASSM 在块之间分散插入以避免并发问题。

高水位线 (HWM) 是数据块未格式化且从未使用过的段中的点。在 HWM 之下,一个块可的状态可以是已格式化和写入、格式化但为空,或者或未格式化。低高水位线 (low HWM) 标记了所有块都被格式化的点,因为它们的状态可能是包含数据或之前包含过数据。

在全表扫描期间,数据库读取所有已知已格式化的低 HWM 块,然后读取段位图以确定 HWM 和低 HWM 之间的哪些块已格式化并且可以安全读取。数据库不会去读取 HWM之外的块,因为这些块未格式化。

8.2.1.2 Importance of Rowids for Row Access

堆组织表中的每一行都有一个该表唯一的 rowid,它对应于行块的物理地址。 rowid 是一行的 10 字节物理地址。

rowid 指向特定的文件、块和行号。例如,在 rowid AAAPecAAFAAAABSAAA 中,最后的 AAA 表示行号。行号是行目录条目的索引。行目录条目包含指向块上行位置的指针。

数据库有时可以在块的底部移动一行。例如,如果启用了行移动,那么行可以因为分区键更新、闪回表操作、收缩表操作等而移动。如果数据库在块内移动一行,则数据库更新行目录条目以修改指针。 rowid 保持不变。

Oracle 数据库在内部使用 rowid 来构建索引。例如,B 树索引中的每个键都与指向关联行地址的 rowid 相关联。物理 rowid 提供对表行的最快访问,使数据库能够在一次 I/O 中检索一行。

8.2.1.3 Direct Path Reads

在直接路径(direct path)读取中,数据库将缓冲区从磁盘直接读取到 PGA 中,完全绕过 SGA。

下图显示了分散读取和顺序读取(将缓冲区存储在 SGA 中)与直接路径读取之间的区别。
在这里插入图片描述
Oracle 数据库可能执行直接路径读取的情况包括:

  • 执行 CREATE TABLE AS SELECT 语句
  • 执行 ALTER REBUILD 或 ALTER MOVE 语句
  • 从临时表空间读取
  • 并行查询
  • 从 LOB 段读取

8.2.2 Full Table Scans

全表扫描从表中读取所有行,然后过滤掉那些不符合选择条件的行。

8.2.2.1 When the Optimizer Considers a Full Table Scan

一般来说,优化器在没有其他的访问路径可选择,或者另一个可用的访问路径成本较高时,会选择全表扫描。

下表显示了选择全表扫描的典型原因。

  • 无索引
    如果索引不存在,则优化器使用全表扫描。
  • 查询谓词将函数应用于索引列
    除非索引是基于函数的索引,否则数据库会索引列的值,而不是列应用函数后的值。 一个典型的应用程序级错误是索引一个字符列,例如 char_col,然后使用 WHERE char_col=1 等语法查询该列。 数据库隐式地将 TO_NUMBER 函数应用于常数 1,从而阻止使用索引。
  • 发出 SELECT COUNT(*) 查询,虽存在索引,但索引列包含空值
    此时优化器不能使用索引来计算表行数,因为索引不能包含空条目。
  • 查询谓词无法使用 B 树索引的前导部分
    例如,员工(first_name,last_name)上可能存在索引。 如果用户使用谓词 WHERE last_name=‘KING’ 发出查询,则优化器可能不会选择索引,因为列 first_name 不在谓词中。 但是,在这种情况下,优化器可能会选择使用索引跳过扫描。
  • 查询的选择性不强
    如果优化器确定查询需要表中的大部分块,那么即使索引可用,它也会使用全表扫描。 全表扫描可以使用更大的 I/O 调用。 进行较少的大型 I/O 调用比进行许多较小的调用代价更低。
  • 表统计信息已过时
    例如,一张表很小,但现在已经变大了。 如果表统计信息过时并且不能反映表的当前大小,则优化器不知道索引现在比全表扫描最有效。
  • 表很小
    如果表在高水位线下包含少于 n 个块,其中 n 等于 DB_FILE_MULTIBLOCK_READ_COUNT 初始化参数的设置,则全表扫描可能比索引范围扫描便宜。 无论正在访问的表的比例或存在的索引如何,扫描的成本都可能较低。
  • 该表具有高度的并行性
    表的高度并行性使优化器倾向于全表扫描而不是范围扫描。 查询 ALL_TABLES.DEGREE 列中的值以确定并行度。
  • 该查询使用全表扫描提示。
    提示 FULL(表别名) 指示优化器使用全表扫描。

8.2.2.2 How a Full Table Scan Works

在全表扫描中,数据库顺序读取高水位线下的每个格式化块。 数据库只读取每个块一次。
下图描述了对表段的扫描,显示了扫描如何跳过高水位线以下的未格式化块。
在这里插入图片描述
由于块是相邻的,数据库可以通过使 I/O 调用大于单个块(称为多块读取)来加快扫描速度。 读取调用的大小范围从一个块到 DB_FILE_MULTIBLOCK_READ_COUNT 初始化参数指定的块数。 例如,将此参数设置为 4 会指示数据库在一次调用中最多读取 4 个块。(跳过的那2个块,估计是通过位图知道它们是未格式化的)

在全表扫描期间缓存块的算法很复杂。 例如,数据库缓存块的方式取决于表的大小。

8.2.2.3 Full Table Scan: Example

以下语句使用全表扫描,是因为salary列上没有索引。

SELECT salary 
FROM   hr.employees 
WHERE  salary > 4000;

8.2.3 Table Access by Rowid

rowid 是数据存储位置的内部表示。

行的rowid 指定包含该行的数据文件和数据块以及该行在该块中的位置。 通过指定行 ID 来定位行是检索单行的最快方法,因为它指定了该行在数据库中的确切位置。

注意:Rowids 在不同版本可能会改变。 不建议根据位置访问数据,因为行可以移动。

8.2.3.1 When the Optimizer Chooses Table Access by Rowid

在大多数情况下,数据库在扫描一个或多个索引后通过 rowid 访问表。

但是,rowid 的表访问不一定发生在每次索引扫描之后。 如果索引包含所有需要的列,则可能不需要按 rowid 的访问。

8.2.3.2 How Table Access by Rowid Works

要通过 rowid 访问表,数据库执行多个步骤。

数据库执行以下操作:

  1. 从语句 WHERE 子句或通过一个或多个索引的索引扫描获取选定行的 rowid
    语句中的列若在索引中不存在,则可能需要表访问。
  2. 根据其 rowid 定位表中的每个选定行

8.2.3.3 Table Access by Rowid: Example

以下语句的执行计划中,数据库通过索引中的rowid来返回行中的数据(因为是SELECT *)。
步骤 1 中所示的 BATCHED 访问是指数据库从索引中检索多个 rowid,然后尝试按块顺序访问行,以提高聚类并减少数据库必须访问块的次数。

SELECT * 
FROM   employees 
WHERE  employee_id > 190;-----------------------------------------------------------------------------------------------------
| Id  | Operation                           | Name          | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                    |               |    16 |  1104 |     2   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID BATCHED| EMPLOYEES     |    16 |  1104 |     2   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN                  | EMP_EMP_ID_PK |    16 |       |     1   (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------

8.2.4 Sample Table Scans

样本表扫描从简单表或复杂 SELECT 语句(例如涉及连接和视图的语句)中检索数据的随机样本。

8.2.4.1 When the Optimizer Chooses a Sample Table Scan

当语句 FROM 子句包含 SAMPLE 关键字时,数据库使用样本表扫描。

SAMPLE 子句具有以下形式:

  • SAMPLE (sample_percent)
    数据库读取表中指定百分比的行以执行样本表扫描。
  • SAMPLE BLOCK (sample_percent)
    数据库读取指定百分比的表块以执行样本表扫描。

sample_percent 指定要包含在样本中的总行数或块数的百分比。 该值必须在 0.000001 到(但不包括)100 的范围内。此百分比表示为样本选择块抽样中的每一行或每一行集群的概率。 这并不意味着数据库准确地检索 sample_percent 行。

注意:只有在全表扫描或索引快速全扫描期间才可以进行块采样。 如果存在更有效的执行路径,则数据库不会对块进行采样。 要保证对特定表或索引进行块采样,请使用 FULL 或 INDEX_FFS 提示。

8.2.4.2 Sample Table Scans: Example

此示例使用示例表扫描访问员工表 1% 的数据,按块而不是行进行采样。此表共107行。

---------------------------------------------------------------------------------
| Id  | Operation           | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------
|   0 | SELECT STATEMENT    |           |       |       |     2 (100)|          |
|   1 |  TABLE ACCESS SAMPLE| EMPLOYEES |     1 |    69 |     2   (0)| 00:00:01 |
---------------------------------------------------------------------------------

8.2.5 In-Memory Table Scans

In-Memory 扫描从 In-Memory Column Store(IM 列存储)中检索行。

IM 列存储是一个可选的 SGA 区域,它以针对快速扫描优化的特殊列格式存储表和分区的副本。

8.2.5.1 When the Optimizer Chooses an In-Memory Table Scan

优化器成本模型知道 IM 列存储的内容。

当用户执行引用 IM 列存储中的表的查询时,优化器会计算所有可能的访问方法(包括内存中表扫描)的成本,并选择成本最低的访问方法。

8.2.5.2 In-Memory Query Controls

您可以使用初始化参数控制 In-Memory 查询。

以下数据库初始化参数会影响 In-Memory 功能:

  • INMEMORY_QUERY
    此参数在会话或系统级别启用或禁用数据库的内存中查询。 当您想要测试启用和禁用 IM 列存储的工作负载时,此参数很有帮助。

  • OPTIMIZER_INMEMORY_AWARE
    此参数启用 (TRUE) 或禁用 (FALSE) 对优化器成本模型、表扩展、布隆过滤器等所做的所有内存增强。 将该参数设置为 FALSE 会导致优化器在 SQL 语句优化期间忽略表的 In-Memory 属性。

  • OPTIMIZER_FEATURES_ENABLE
    当设置为低于 12.1.0.2 的值时,此参数与将 OPTIMIZER_INMEMORY_AWARE 设置为 FALSE 的效果相同。

要启用或禁用 In-Memory 查询,您可以指定 INMEMORY 或 NO_INMEMORY 提示,它们是 INMEMORY_QUERY 初始化参数的按查询设置的等效项。 如果 SQL 语句使用 INMEMORY 提示,但它引用的对象尚未加载到 IM 列存储中,则数据库在执行语句之前不会等待对象填充到 IM 列存储中。 但是,对象的初始访问会触发 IM 列存储中的对象填充。

8.2.5.3 In-Memory Table Scans: Example

此略。执行计划的Operation中会出现TABLE ACCESS INMEMORY FULL关键字。

8.3 B-Tree Index Access Paths

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

通过在表的一个或多个列上创建索引,您可以在某些情况下从表中检索一小组随机分布的行。 索引是减少磁盘 I/O 的众多方法之一。

8.3.1 About B-Tree Index Access

B树是平衡树的缩写,是最常见的数据库索引类型。

B-tree 索引是一个有序的值列表,分为多个范围。 通过将键与行或行范围相关联,B 树为广泛的查询提供了出色的检索性能,包括精确匹配和范围搜索。

8.3.1.1 B-Tree Index Structure

B-tree 索引有两种类型的块:用于搜索的分支块和存储值的叶块。

下图说明了 B 树索引的逻辑结构。 分支块存储在两个键之间做出分支决策所需的最小键前缀。 叶块包含每个索引数据值和用于定位实际行的相应 rowid。 每个索引条目都按 (key, rowid) 排序。 叶块是双向链接的。
在这里插入图片描述

8.3.1.2 How Index Storage Affects Index Scans

位图索引块可以出现在索引段的任何位置。

上图显示了叶块彼此相邻。 例如,1-10 块在 11-19 块的旁边和之前。 此排序说明了连接索引条目的链表。 但是,索引块不需要按顺序存储在索引段内。 例如,246-250 块可以出现在段中的任何位置,包括直接在 1-10 块之前。 因此,有序索引扫描必须执行单块 I/O。 数据库必须读取一个索引块以确定它接下来必须读取哪个索引块。
索引块体将索引条目存储在堆中,就像表行一样。 例如,如果首先将值 10 插入到表中,那么键为 10 的索引条目可能会插入到索引块的底部。 如果接下来将 0 插入表中,则键 0 的索引条目可能会插入到 10 的条目之上。因此,块主体中的索引条目不是按键顺序存储的。 但是,在索引块中,行头按键顺序存储记录。 例如,头中的第一条记录指向键为 0 的索引条目,依此类推,直到指向键为 10 的索引条目的记录。因此,索引扫描可以读取行头以确定从哪里开始和结束范围扫描,避免读取块中的每个条目。

8.3.1.3 Unique and Nonunique Indexes

在非唯一索引中,数据库通过将 rowid 作为额外列附加到键来存储它。 该条目添加一个长度字节以使索引键唯一。

例如,上图所示的非唯一索引中的第一个索引键是 0,rowid 而不是简单的 0。数据库按索引键值对数据进行排序,然后按 rowid 升序排列。 例如,条目排序如下:

0,AAAPvCAAFAAAAFaAAa
0,AAAPvCAAFAAAAFaAAg
0,AAAPvCAAFAAAAFaAAl
2,AAAPvCAAFAAAAFaAAm

8.3.1.4 B-Tree Indexes and Nulls

B-tree 索引从不存储所有键为空的条目,这对于优化器如何选择访问路径很重要。 此规则的结果是单列 B 树索引从不存储空值。
一个例子有助于说明。 hr.employees 表在employee_id 上有一个主键索引,在department_id 上有一个唯一索引。 department_id 列可以包含空值,使其成为可为空的列,但employee_id 列不能。

SQL> SELECT COUNT(*) FROM employees WHERE department_id IS NULL;COUNT(*)
----------1SQL> SELECT COUNT(*) FROM employees WHERE employee_id IS NULL;COUNT(*)
----------0

所以其执行计划是不同的:

-- 全表扫描
EXPLAIN PLAN FOR SELECT department_id FROM employees;
SELECT PLAN_TABLE_OUTPUT FROM TABLE(DBMS_XPLAN.DISPLAY());-- 索引范围扫描
EXPLAIN PLAN FOR SELECT department_id FROM employees WHERE department_id=10;
SELECT PLAN_TABLE_OUTPUT FROM TABLE(DBMS_XPLAN.DISPLAY());-- 排除掉NULL后,仍可以使用索引扫描:INDEX FULL SCAN
EXPLAIN PLAN FOR SELECT department_id FROM employees 
WHERE department_id IS NOT NULL;
SELECT PLAN_TABLE_OUTPUT FROM TABLE(DBMS_XPLAN.DISPLAY());

8.3.2 Index Unique Scans

索引唯一扫描最多返回 1 个 rowid。

8.3.2.1 When the Optimizer Considers Index Unique Scans

索引唯一扫描需要相等谓词。

具体来说,仅当查询谓词使用相等运算符(例如 WHERE prod_id=10)引用唯一索引键中的所有列时,数据库才会执行唯一扫描。

唯一或主键约束本身不足以产生索引唯一扫描,因为列上的非唯一索引可能已经存在。 考虑以下示例,该示例创建 t_table 表,然后在 numcol 上创建非唯一索引:

DROP TABLE t_table;
CREATE TABLE t_table(numcol INT);
CREATE INDEX t_table_idx ON t_table(numcol);
SELECT UNIQUENESS FROM USER_INDEXES WHERE INDEX_NAME = 'T_TABLE_IDX';UNIQUENES
---------
NONUNIQUE

以下代码在具有非唯一索引的列上创建主键约束,从而导致索引范围扫描而不是索引唯一扫描:

ALTER TABLE t_table ADD CONSTRAINT t_table_pk PRIMARY KEY(numcol);
SET AUTOTRACE TRACEONLY EXPLAIN
SELECT * FROM t_table WHERE numcol = 1;--------------------------------------------------------------------------------
| Id  | Operation        | Name        | Starts | E-Rows | A-Rows |   A-Time   |
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT |             |      1 |        |      0 |00:00:00.01 |
|*  1 |  INDEX RANGE SCAN| T_TABLE_IDX |      1 |      1 |      0 |00:00:00.01 |
--------------------------------------------------------------------------------

您可以使用 INDEX(alias index_name) 提示指定要使用的索引,但不能指定特定类型的索引访问路径。

这个例子太奇怪了,按说定义了主键后应该删除非唯一索引。

8.3.2.2 How Index Unique Scans Work

扫描按顺序搜索索引以查找指定的键。 索引唯一扫描在找到第一条记录后立即停止处理,因为不可能有第二条记录。 数据库从索引条目中获取rowid,然后检索rowid指定的行。

下图说明了索引唯一扫描。 该语句请求 prod_id 列中产品 ID 19 的记录,该列具有主键索引。
在这里插入图片描述

8.3.2.3 Index Unique Scans: Example

此示例使用唯一扫描从 products 表中检索一行。

SELECT * 
FROM   sh.products 
WHERE  prod_id = 19;-------------------------------------------------------------------------------------------
| Id  | Operation                   | Name        | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |             |     1 |   173 |     1   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| PRODUCTS    |     1 |   173 |     1   (0)| 00:00:01 |
|*  2 |   INDEX UNIQUE SCAN         | PRODUCTS_PK |     1 |       |     0   (0)| 00:00:01 |
-------------------------------------------------------------------------------------------

8.3.3 Index Range Scans

索引范围扫描是值的有序扫描。

扫描的范围可以在两侧有界(例如>=和<=),也可以在一侧或两侧无界(例如>和<)。 优化器通常为具有高选择性的查询选择范围扫描。

默认情况下,数据库以升序存储索引,并以相同的顺序扫描它们。 例如,谓词 department_id >= 20 的查询使用范围扫描返回按索引键 20、30、40 等排序的行。 如果多个索引条目具有相同的键,则数据库按 rowid 升序返回它们,因此 0,AAAPvCAAFAAAAFaAAAa 后面是 0,AAAPvCAAFAAAAFaAAg,依此类推。

降序索引范围扫描与索引范围扫描相同,只是数据库按降序返回行。 通常,当以降序对数据进行排序或查找小于指定值的值时,数据库会使用降序扫描。

8.3.3.1 When the Optimizer Considers Index Range Scans

对于索引范围扫描,索引键必须可以有多个值。

具体来说,优化器会在以下情况下考虑索引范围扫描:

  • 在条件中指定索引的一个或多个前导列。
    条件指定一个或多个表达式和逻辑(布尔)运算符的组合,并返回 TRUE、FALSE 或 UNKNOWN 值。
  • 索引键可以有 0个、1个 或更多值。

提示:如果您需要排序数据,请使用 ORDER BY 子句,不要依赖索引。 如果索引可以满足 ORDER BY 子句,则优化器使用此选项,从而避免排序。

当索引可以满足 ORDER BY DESCENDING 子句时,优化器会考虑降序索引范围扫描。

如果优化器选择全表扫描或其他索引,则可能需要提示来强制执行此访问路径。 INDEX(tbl_alias ix_name) 和 INDEX_DESC(tbl_alias ix_name) 提示指示优化器使用特定索引。

8.3.3.2 How Index Range Scans Work

在索引范围扫描期间,Oracle 数据库从根到分支行进。

一般来说,扫描算法如下:

  1. 读取根块。
  2. 读取分支块。
  3. 交替执行以下步骤,直到检索到所有数据:
    a. 读取叶子块以获取 rowid。
    b. 读取表块以检索行。

注意:在某些情况下,索引扫描会读取一组索引块,对 rowid 进行排序,然后再读取一组表块。

因此,为了扫描索引,数据库通过叶子块向后或向前移动。 例如,对 20 到 40 之间的 ID 的扫描会定位第一个具有最小键值为 20 或更大的叶块。 扫描在叶节点链表中水平进行,直到找到大于 40 的值,然后停止。

下图说明了使用升序的索引范围扫描。 一条语句请求department_id 列中值为20 的员工记录,该列具有非唯一索引。 在此示例中,部门 20 存在 2 个索引条目。
在这里插入图片描述

8.3.3.3 Index Range Scan: Example

此示例使用索引范围扫描从员工表中检索一组值。

SELECT * 
FROM   employees 
WHERE  department_id = 20
AND    salary > 1000;---------------------------------------------------------------------------------------------------------
| Id  | Operation                           | Name              | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                    |                   |     2 |   138 |     2   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS BY INDEX ROWID BATCHED| EMPLOYEES         |     2 |   138 |     2   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN                  | EMP_DEPARTMENT_IX |     2 |       |     1   (0)| 00:00:01 |
---------------------------------------------------------------------------------------------------------

8.3.3.4 Index Range Scan Descending: Example

此示例使用索引按排序顺序从 employees 表中检索行。

SELECT *
FROM   employees
WHERE  department_id < 20
ORDER BY department_id DESC;--------------------------------------------------------------------------------------------------
| Id  | Operation                    | Name              | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |                   |     2 |   138 |     2   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID | EMPLOYEES         |     2 |   138 |     2   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN DESCENDING| EMP_DEPARTMENT_IX |     2 |       |     1   (0)| 00:00:01 |
--------------------------------------------------------------------------------------------------

数据库定位第一个索引叶子块,该块包含 20 或更少的最高键值。 然后扫描通过叶节点的链表水平向左进行。 数据库从每个索引条目中获取rowid,然后检索rowid指定的行。

8.3.4 Index Full Scans

索引全扫描按顺序读取整个索引。 索引全扫描可以消除单独的排序操作,因为索引中的数据是按索引键排序的。

8.3.4.1 When the Optimizer Considers Index Full Scans

优化器会在多种情况下考虑索引全扫描。

这些情况包括:

  • 谓词引用索引中的列。 此列不必是前导列。
  • 未指定谓词,但满足以下所有条件:
    • 表和查询中的所有列都在索引中。(似乎和后面的例子不符)
    • 至少一个索引列不为空。
  • 查询在索引的不可为空的列上包含 ORDER BY。

8.3.4.2 How Index Full Scans Work

数据库读取根块,然后下行到索引的左侧(如果进行降序全扫描,则为右侧)直到到达叶块。

然后数据库到达一个叶子块,扫描在索引的底部进行,一次一个块,按排序顺序。 数据库使用单块 I/O 而不是多块 I/O。

下图说明了索引全扫描。 一条语句请求按 department_id 排序的部门记录。
在这里插入图片描述

8.3.4.3 Index Full Scans: Example

此示例使用索引全扫描来满足带有 ORDER BY 子句的查询。

SELECT department_id, department_name
FROM   departments
ORDER BY department_id;-------------------------------------------------------------------------------------------
| Id  | Operation                   | Name        | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |             |    27 |   432 |     2   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| DEPARTMENTS |    27 |   432 |     2   (0)| 00:00:01 |
|   2 |   INDEX FULL SCAN           | DEPT_ID_PK  |    27 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------------

由于department_name不在索引中,所以还需通过rowid去查询表,不过仍避免了排序。

8.3.5 Index Fast Full Scans

索引快速全扫描以未排序的顺序读取索引块,因为它们存在于磁盘上。 这种扫描不使用索引来探查表,而是读取索引而不是表,本质上是使用索引本身作为表。

8.3.5.1 When the Optimizer Considers Index Fast Full Scans

当查询仅访问索引中的属性时,优化器会考虑此扫描。

注意:与全扫描不同,快速全扫描不能消除排序操作,因为它不会按顺序读取索引。
INDEX_FFS(table_name index_name) 提示强制进行快速全索引扫描。

8.3.5.2 How Index Fast Full Scans Work

数据库使用多块 I/O 来读取根块以及所有叶和分支块。 数据库忽略分支和根块并读取叶块上的索引条目。

8.3.5.3 Index Fast Full Scans: Example

此示例使用快速全索引扫描作为优化器提示的结果。

EXPLAIN PLAN FOR
SELECT /*+ INDEX_FFS(departments dept_id_pk) */ COUNT(*)
FROM   departments;----------------------------------------------------------------------------
| Id  | Operation             | Name       | Rows  | Cost (%CPU)| Time     |
----------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |            |     1 |     2   (0)| 00:00:01 |
|   1 |  SORT AGGREGATE       |            |     1 |            |          |
|   2 |   INDEX FAST FULL SCAN| DEPT_ID_PK |    27 |     2   (0)| 00:00:01 |
------------------------------------------------------------------------------ 若不指定hint,则为INDEX FULL SCAN
-----------------------------------------------------------------------
| Id  | Operation        | Name       | Rows  | Cost (%CPU)| Time     |
-----------------------------------------------------------------------
|   0 | SELECT STATEMENT |            |     1 |     1   (0)| 00:00:01 |
|   1 |  SORT AGGREGATE  |            |     1 |            |          |
|   2 |   INDEX FULL SCAN| DEPT_ID_PK |    27 |     1   (0)| 00:00:01 |
-----------------------------------------------------------------------

8.3.6 Index Skip Scans

当复合索引的初始列被“跳过”或未在查询中指定时,会发生索引跳过扫描。

8.3.6.1 When the Optimizer Considers Index Skip Scans

通常,跳过扫描索引块比扫描表块更快,并且比执行完整索引扫描更快。

当满足以下条件时,优化器会考虑跳过扫描:

  • 查询谓词中未指定复合索引的前导列。
    例如,查询谓词不引用 cust_gender 列,复合索引键为 (cust_gender,cust_email)。

  • 索引的非前导键中存在许多不同的值,而前导键中存在的不同值相对较少。
    例如,如果复合索引键是 (cust_gender,cust_email),那么 cust_gender 列只有两个不同的值,但 cust_email 有数千个。

8.3.6.2 How Index Skip Scans Work

索引跳过扫描在逻辑上将复合索引拆分为更小的子索引。

索引前导列中不同值的数量决定了逻辑子索引的数量。 数字越小,优化器必须创建的逻辑子索引越少,扫描的效率就越高。 扫描单独读取每个逻辑索引,并“跳过”非前导列上不满足过滤条件的索引块。

8.3.6.3 Index Skip Scans: Example

以下示例需要新建索引,为不破坏示例schema,就不执行了:

CREATE INDEX cust_gender_email_ixON sh.customers (cust_gender, cust_email);

原理上就是将以下SQL:

SELECT * 
FROM   sh.customers 
WHERE  cust_email = 'Abbey@company.example.com';

转换为以下:

( 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' )

8.3.7 Index Join Scans

索引连接扫描是多个索引的哈希连接,它们一起返回查询请求的所有列。 数据库不需要访问表,因为所有数据都是从索引中检索的。

8.3.7.1 When the Optimizer Considers Index Join Scans

在某些情况下,避免表访问是最具成本效益的选择。

优化器在以下情况下考虑索引连接:

  • 多个索引的哈希连接检索查询请求的所有数据,而不需要访问表。
  • 从表中检索行的成本高于在不从表中检索行的情况下读取索引的成本。 索引连接通常很昂贵。 例如,当扫描两个索引并将它们连接起来时,选择最有选择性的索引然后探测表通常成本更低。

您可以使用 INDEX_JOIN(table_name) 提示指定索引连接。

8.3.7.2 How Index Join Scans Work

索引连接涉及扫描多个索引,然后对从这些扫描获得的 rowid 使用哈希连接以返回行。

在索引连接扫描中,总是避免表访问。 例如,在一个表上连接两个索引的过程如下:

  1. 扫描第一个索引以检索 rowid。
  2. 扫描第二个索引以检索 rowid。
  3. 按 rowid 执行哈希联接以获取行。

8.3.7.3 Index Join Scans: Example

SELECT /*+ INDEX_JOIN(employees) */ last_name, email
FROM   employees
WHERE  last_name like 'A%';-------------------------------------------------------------------------------------------
| Id  | Operation              | Name             | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT       |                  |     3 |    48 |     2   (0)| 00:00:01 |
|*  1 |  VIEW                  | index$_join$_001 |     3 |    48 |     2   (0)| 00:00:01 |
|*  2 |   HASH JOIN            |                  |       |       |            |          |
|*  3 |    INDEX RANGE SCAN    | EMP_NAME_IX      |     3 |    48 |     1   (0)| 00:00:01 |
|   4 |    INDEX FAST FULL SCAN| EMP_EMAIL_UK     |     3 |    48 |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------------

8.4 Bitmap Index Access Paths

位图索引将索引数据与 rowid 范围相结合。

8.4.1 About Bitmap Index Access

在传统的 B 树索引中,一个索引条目指向单行。 在位图索引中,键是索引数据和 rowid 范围的组合。

数据库为每个索引键存储至少一个位图。 位图中的每个值,即一系列 1 和 0 值,都指向 rowid 范围内的一行。 因此,在位图索引中,一个索引条目指向一组行而不是单行。

8.4.1.1 Differences Between Bitmap and B-Tree Indexes

位图索引使用与 B-tree 索引不同的键,但存储在 B-tree 结构中。

下表显示了索引条目类型之间的差异。
在这里插入图片描述

数据库将位图索引存储在 B 树结构中。 数据库可以在key的第一部分,即定义索引的属性集上快速搜索B-tree,然后得到对应的rowid范围和位图。

8.4.1.2 Purpose of Bitmap Indexes

位图索引通常适用于具有低或中等数量的不同值 (NDV) ,且不经常修改的数据。

一般来说,B-tree 索引适用于具有高 NDV 和频繁 DML 活动的列。 例如,优化器可能会选择一个 B 树索引来查询一个返回几行的 sales.amount 列。 相比之下,customers.state 和 customers.county 列是位图索引的候选者,因为它们的不同值很少,不经常更新,并且可以从高效的 AND 和 OR 操作中受益。

位图索引是一种在数据仓库中加速即席查询的有用方法。 它们是星型转换的基础。 具体来说,位图索引在包含以下内容的查询中很有用:

  • WHERE 子句中的多个条件
    在访问表本身之前,数据库会过滤掉满足某些(但不是全部)条件的行。
  • 对具有低或中等 NDV 的列进行 AND、OR 和 NOT 运算
    组合位图索引使这些操作更有效。 数据库可以非常快速地合并来自位图索引的位图。 例如,如果customers.state 和customers.county 列上存在位图索引,那么这些索引可以极大地提高以下查询的性能:
SELECT * 
FROM   customers 
WHERE  state = 'CA' 
AND    county = 'San Mateo'

数据库可以有效地将合并位图中的 1 值转换为 rowid。

  • 计数函数
    数据库可以扫描位图索引而不需要扫描表。
  • 选择空值的谓词
    与 B 树索引不同,位图索引可以包含空值。 计算列中空值数量的查询可以使用位
    图索引,而无需扫描表。(如果某列a为空,count(a)不会计算空值,但count(*)会。)
  • 未经历重度 DML 的列
    原因是一个索引键指向多行。 如果会话修改了索引数据,则数据库无法锁定位图中的单个位:相反,数据库锁定整个索引条目,这实际上锁定了位图指向的多行。 例如,如果特定客户的居住县从 San Mateo 更改为 Alameda,则数据库必须获得对位图中的 San Mateo 索引条目和 Alameda 索引条目的独占访问权。 直到 COMMIT 才能修改包含这两个值的行。

8.4.1.3 Bitmaps and Rowids

对于位图中的特定值,如果行值与位图条件匹配,则值为 1,否则为 0。 基于这些值,数据库使用内部算法将位图映射到 rowid。

位图条目包含索引值、rowid 范围(开始和结束 rowid)和位图。 位图中的每个 0 或 1 值都是 rowid 范围的偏移量,并映射到表中的潜在行,即使该行不存在。 因为块中可能的行数是预先确定的,所以数据库可以使用范围端点来确定范围中任意行的 rowid。

Hakan 因子是位图索引算法用来限制 Oracle 数据库假定可以存储在单个块中的行数的优化。 通过人为地限制行数,数据库减小了位图的大小。

表 8-4 显示了 sh.customers.cust_marital_status 列的示例位图的一部分,该列可以为空。 实际索引有 12 个不同的值。 样本中只显示了 3 个:null、已婚和单身。
在这里插入图片描述
如表 8-4 所示,位图索引可以包含完全由空值组成的键,这与 B 树索引不同。 在表 8-4 中,范围内第 6 行的 null 值为 1,这意味着范围内第 6 行的 cust_marital_status 值为 null。 索引空值对于某些 SQL 语句可能很有用,例如使用聚合函数 COUNT 的查询。

8.4.1.4 Bitmap Join Indexes

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

优化器可以使用位图联结索引来减少或消除计划执行期间必须联结的数据量。 位图联结索引在存储方面比物化联结视图更有效。

以下示例在 sh.sales 和 sh.customers 表上创建位图索引:

CREATE BITMAP INDEX cust_sales_bji ON sales(c.cust_city) FROM sales s, customers c WHERE c.cust_id = s.cust_id LOCAL;

前面 CREATE 语句中的 FROM 和 WHERE 子句表示表之间的连接条件。 customers.cust_city 列是索引键。

索引中的每个键值代表客户表中的一个可能城市。 从概念上讲,索引的键值可能如下所示,每个键值关联一个位图:

San Francisco   0 0 0 1 0 1 0 0 0 1 0 0 0 0 0 . . .
San Mateo       0 1 0 0 0 0 0 0 1 0 0 0 0 0 1 . . .
Smithville      1 0 0 0 1 0 0 1 0 0 1 0 1 0 0 . . .
.
.
.

位图中的每一位对应于销售表中的一行。 在 Smithville 键中,值 1 表示销售表中的第一行对应于销售给 Smithville 客户的产品,而值 0 表示第二行对应于未销售给 Smithville 客户的产品。

SELECT COUNT (*)
FROM   sales s, customers c
WHERE  c.cust_id = s.cust_id
AND    c.cust_city = 'Smithville';

考虑以下对 Smithville 客户的单独销售数量的查询:

SELECT COUNT (*)
FROM   sales s, customers c
WHERE  c.cust_id = s.cust_id
AND    c.cust_city = 'Smithville';

以下计划显示数据库读取 Smithville 位图以得出 Smithville 销售数量(步骤 4),从而避免连接客户和销售表。

------------------------------------------------------------------------------------
|Id| Operation                    | Name |Rows|Bytes|Cost (%CPU)| Time|Pstart|Pstop|
------------------------------------------------------------------------------------
| 0| SELECT STATEMENT             |              |     |    |29 (100)|        | |  |
| 1|  SORT AGGREGATE              |              |   1 |   5|        |        | |  |
| 2|   PARTITION RANGE ALL        |              | 1708|8540|29   (0)|00:00:01|1|28|
| 3|    BITMAP CONVERSION COUNT   |              | 1708|8540|29   (0)|00:00:01| |  |
|*4|     BITMAP INDEX SINGLE VALUE|CUST_SALES_BJI|     |    |        |        |1|28|
------------------------------------------------------------------------------------Predicate Information (identified by operation id):
---------------------------------------------------4 - access("S"."SYS_NC00008$"='Smithville')

8.4.1.5 Bitmap Storage

位图索引位于 B 树结构中,使用分支块和叶块,就像在 B 树中一样。

例如,如果customers.cust_marital_status 列有12 个不同的值,那么一个分支块可能指向键married,rowid-range 和single,rowid-range,另一个分支块可能指向widowed,rowid-range 键。 或者,单个分支块可以指向包含所有 12 个不同键的叶块。

每个索引列值可能有一个或多个位图片段,每个片段都有自己的 rowid 范围,在一个或多个范围中占据一组连续的行。 数据库可以使用位图片段来分解相对于块大小而言较大的索引条目。 例如,数据库可以将单个索引条目分成三个部分,前两个部分在相同范围内的单独块中,最后一个部分在不同范围内的单独块中。

为了节省空间,Oracle 数据库可以压缩 0 值的连续范围。

8.4.2 Bitmap Conversion to Rowid

位图转换在位图中的条目和表中的行之间进行转换。 转换可以从条目到行 (TO ROWID),或从行到条目 (FROM ROWID)。

8.4.2.1 When the Optimizer Chooses Bitmap Conversion to Rowid

优化器在使用位图索引条目从表中检索行时使用转换。

8.4.2.2 How Bitmap Conversion to Rowid Works

从概念上讲,位图可以表示为表格。

例如,表 8-4 将位图表示为一个表,其中客户行号作为列,cust_marital_status 值作为行。 表 8-4 中的每个字段都有值 1 或 0,表示一行中的一个列值。 从概念上讲,位图转换使用一种内部算法,即“位图中的字段 F 对应于表的第 M 块的第 N 行”或“表中的第 M 块的第 N 行对应于表中的字段 F 位图。”

8.4.2.3 Bitmap Conversion to Rowid: Example

在此示例中,优化器选择位图转换操作来满足使用范围谓词的查询。

对 sh.customers 表的查询选择 1918 年之前出生的所有客户的姓名:

SELECT cust_last_name, cust_first_name
FROM   customers
WHERE  cust_year_of_birth < 1918;

下面的计划显示,数据库使用范围扫描查找所有小于 1918 的键值(步骤 3),将位图中的 1 值转换为 rowids(步骤 2),然后使用 rowids 从客户那里获取行 表(步骤 1):

-------------------------------------------------------------------------------------------
|Id| Operation                           | Name             |Rows|Bytes|Cost(%CPU)| Time  |
-------------------------------------------------------------------------------------------
| 0| SELECT STATEMENT                    |                  |    |     |421 (100)|        |
| 1|  TABLE ACCESS BY INDEX ROWID BATCHED| CUSTOMERS        |3604|68476|421   (1)|00:00:01|
| 2|   BITMAP CONVERSION TO ROWIDS       |                  |    |     |         |        |
|*3|    BITMAP INDEX RANGE SCAN          | CUSTOMERS_YOB_BIX|    |     |         |        |
-------------------------------------------------------------------------------------------Predicate Information (identified by operation id):
---------------------------------------------------3 - access("CUST_YEAR_OF_BIRTH"<1918)filter("CUST_YEAR_OF_BIRTH"<1918)

8.4.3 Bitmap Index Single Value

这种类型的访问路径使用位图索引来查找单个键值。

8.4.3.1 When the Optimizer Considers Bitmap Index Single Value

当谓词包含相等运算符时,优化器会考虑此访问路径。

8.4.3.2 How Bitmap Index Single Value Works

该查询扫描单个位图以查找包含 1 值的位置。 数据库将 1 值转换为 rowid,然后使用 rowid 查找行。

数据库只需要处理一个位图。 例如,下表表示 sh.customers.cust_marital_status 列中寡居的值的位图索引(在两个位图中)。 为了满足状态为寡的客户的查询,数据库可以在寡的位图中搜索每个1的值,找到对应行的rowid。
在这里插入图片描述

8.4.3.3 Bitmap Index Single Value: Example

在此示例中,优化器选择位图索引单值操作来满足使用相等谓词的查询。

对 sh.customers 表的查询会选择所有寡居的客户:

SELECT * 
FROM   customers 
WHERE  cust_marital_status = 'Widowed';

下面的方案展示了数据库读取customers位图索引中带有Widowed键的条目(步骤3),将位图中的1值转换为rowids(步骤2),然后使用rowids从customers处获取行 表(步骤 1):

-------------------------------------------------------------------------------------------
|Id| Operation                          | Name               |Rows|Bytes|Cost (%CPU)| Time|
-------------------------------------------------------------------------------------------
| 0|SELECT STATEMENT                    |                     |    |    |412(100)|        |
| 1| TABLE ACCESS BY INDEX ROWID BATCHED|CUSTOMERS            |3461|638K|412  (2)|00:00:01|
| 2|  BITMAP CONVERSION TO ROWIDS       |                     |    |    |        |        |
|*3|   BITMAP INDEX SINGLE VALUE        |CUSTOMERS_MARITAL_BIX|    |    |        |        |
-------------------------------------------------------------------------------------------Predicate Information (identified by operation id):
---------------------------------------------------3 - access("CUST_MARITAL_STATUS"='Widowed')

8.4.4 Bitmap Index Range Scans

这种类型的访问路径使用位图索引来查找一系列值。

8.4.4.1 When the Optimizer Considers Bitmap Index Range Scans

当谓词选择一系列值时,优化器会考虑此访问路径。

扫描的范围可以在两侧有界,也可以在一侧或两侧无界。 优化器通常为选择性的查询选择范围扫描。

8.4.4.2 How Bitmap Index Range Scans Work

此扫描的工作方式类似于 B 树范围扫描。

例如,下表表示 sh.customers.cust_year_of_birth 列的位图索引中的三个值。 如果查询请求所有出生于 1917 年之前的客户,则数据库可以扫描此索引以查找低于 1917 的值,然后获取具有 1 的行的 rowid。
在这里插入图片描述

8.4.4.3 Bitmap Index Range Scans: Example

此示例使用范围扫描来选择某一年之前出生的客户。

对 sh.customers 表的查询选择出生于 1918 年之前的客户的姓名:

SELECT cust_last_name, cust_first_name
FROM   customers
WHERE  cust_year_of_birth < 1918

以下计划显示,数据库获取 cust_year_of_birth 低于 1918 的所有位图(步骤 3),将位图转换为 rowid(步骤 2),然后获取行(步骤 1):

-------------------------------------------------------------------------------------------
|Id| Operation                           | Name             |Rows|Bytes|Cost(%CPU)|Time   |
-------------------------------------------------------------------------------------------
| 0| SELECT STATEMENT                    |                  |    |     |421 (100)|        |
| 1|  TABLE ACCESS BY INDEX ROWID BATCHED|CUSTOMERS         |3604|68476|421   (1)|00:00:01|
| 2|   BITMAP CONVERSION TO ROWIDS       |                  |    |     |         |        |
|*3|    BITMAP INDEX RANGE SCAN          |CUSTOMERS_YOB_BIX |    |     |         |        |
-------------------------------------------------------------------------------------------Predicate Information (identified by operation id):
---------------------------------------------------3 - access("CUST_YEAR_OF_BIRTH"<1918)filter("CUST_YEAR_OF_BIRTH"<1918)

8.4.5 Bitmap Merge

此访问路径合并多个位图,并返回单个位图作为结果。

位图合并由执行计划中的 BITMAP MERGE 操作指示。

8.4.5.1 When the Optimizer Considers Bitmap Merge

优化器通常使用位图合并来组合从位图索引范围扫描生成的位图。

8.4.5.2 How Bitmap Merge Works

合并在两个位图之间使用布尔 OR 运算。 生成的位图选择第一个位图中的所有行,以及每个后续位图中的所有行。

查询可能会选择 1918 年之前出生的所有客户。以下示例显示了三个customers.cust_year_of_birth 键的示例位图:1917、1916 和 1915。如果任何位图中的任何位置具有 1,则合并的位图在同一位置具有 1 位置。 否则,合并的位图为 0。

1917     1 0 1 0 0 0 0 0 0 0 0 0 0 1
1916     0 1 0 0 0 0 0 0 0 0 0 0 0 0
1915     0 0 0 0 0 0 0 0 1 0 0 0 0 0
------------------------------------
merged:  1 1 1 0 0 0 0 0 1 0 0 0 0 1

结果位图中的 1 值对应于包含值 1915、1916 或 1917 的行。

8.4.5.3 Bitmap Merge: Example

此示例显示数据库如何合并位图以使用范围谓词优化查询。

对 sh.customers 表的查询选择 1918 年之前出生的女性客户的姓名:

SELECT cust_last_name, cust_first_name
FROM   customers
WHERE  cust_gender = 'F'
AND    cust_year_of_birth < 1918

下面的计划显示,数据库获取所有低于 1918 的 cust_year_of_birth 键的位图(步骤 6),然后使用 OR 逻辑合并这些位图以创建单个位图(步骤 5)。 数据库为 F 的 cust_gender 键获取单个位图(步骤 4),然后对这两个位图执行 AND 操作。 结果是一个位图,其中包含请求行的 1 个值(步骤 3)。

-------------------------------------------------------------------------------------------
|Id| Operation                          | Name               |Rows|Bytes|Cost(%CPU)|Time  |
-------------------------------------------------------------------------------------------
| 0|SELECT STATEMENT                    |                    |    |     |288(100)|        |
| 1| TABLE ACCESS BY INDEX ROWID BATCHED|CUSTOMERS           |1802|37842|288  (1)|00:00:01|
| 2|  BITMAP CONVERSION TO ROWIDS       |                    |    |     |        |        |
| 3|   BITMAP AND                       |                    |    |     |        |        |
|*4|    BITMAP INDEX SINGLE VALUE       |CUSTOMERS_GENDER_BIX|    |     |        |        |
| 5|    BITMAP MERGE                    |                    |    |     |        |        |
|*6|     BITMAP INDEX RANGE SCAN        |CUSTOMERS_YOB_BIX   |    |     |        |        |
-------------------------------------------------------------------------------------------Predicate Information (identified by operation id):
---------------------------------------------------4 - access("CUST_GENDER"='F')6 - access("CUST_YEAR_OF_BIRTH"<1918)filter("CUST_YEAR_OF_BIRTH"<1918)

8.5 Table Cluster Access Paths

表簇是一组共享公共列并将相关数据存储在相同块中的表。 当表被聚集在一起时,一个数据块可以包含来自多个表的行。

8.5.1 Cluster Scans

索引簇是使用索引来定位数据的表簇。

簇索引是簇键上的 B 树索引。 集群扫描从存储在索引集群中的表中检索具有相同集群键值的所有行。

8.5.1.1 When the Optimizer Considers Cluster Scans

当查询访问索引集群中的表时,数据库会考虑集群扫描。

8.5.1.2 How a Cluster Scan Works

在索引集群中,数据库将具有相同集群键值的所有行存储在同一数据块中。

例如,hr.employees2 和 hr.departments2 表在 emp_dept_cluster 中集群,如果集群键是 department_id,那么数据库将部门 10 的所有员工存储在同一个块中,部门 20 的所有员工存储在同一个块中, 等等。

B树集群索引将集群键值与包含数据的块的数据库块地址(DBA)相关联。 例如,键 30 的索引条目显示了包含部门 30 员工行的块的地址:

30,AADAAAA9d

当用户请求集群中的行时,数据库会扫描索引以获取包含这些行的块的 DBA。 Oracle 数据库然后根据这些 DBA 定位行。

8.5.1.3 Cluster Scans: Example

此示例在 department_id 列上对雇员和部门表进行集群,然后在集群中查询单个部门。

作为用户 hr,您创建表簇、簇索引和簇中的表,如下所示:

CREATE CLUSTER employees_departments_cluster(department_id NUMBER(4)) SIZE 512;CREATE INDEX idx_emp_dept_clusterON CLUSTER employees_departments_cluster;CREATE TABLE employees2CLUSTER employees_departments_cluster (department_id)AS SELECT * FROM employees;CREATE TABLE departments2CLUSTER employees_departments_cluster (department_id)AS SELECT * FROM departments;

您查询部门 30 中的员工如下:

SELECT * 
FROM   employees2 
WHERE  department_id = 30;

为了执行扫描,Oracle 数据库首先通过扫描集群索引获取描述部门 30 的行的 rowid(步骤 2)。 然后,Oracle 数据库使用此 rowid 定位 employees2 中的行(步骤 1)。

---------------------------------------------------------------------------
|Id| Operation            | Name               |Rows|Bytes|Cost(%CPU)|Time|
---------------------------------------------------------------------------
| 0| SELECT STATEMENT     |                    |   |    | 2 (100)|        |
| 1|  TABLE ACCESS CLUSTER| EMPLOYEES2         | 6 |798 | 2   (0)|00:00:01|
|*2|   INDEX UNIQUE SCAN  |IDX_EMP_DEPT_CLUSTER| 1 |    | 1   (0)|00:00:01|
---------------------------------------------------------------------------Predicate Information (identified by operation id):
---------------------------------------------------2 - access("DEPARTMENT_ID"=30)

8.5.2 Hash Scans

散列簇类似于索引簇,只是索引键被散列函数替换。 不存在单独的集群索引。

在散列集群中,数据就是索引。 数据库使用散列扫描根据散列值定位散列簇中的行。

8.5.2.1 When the Optimizer Considers a Hash Scan

当查询访问散列集群中的表时,数据库会考虑散列扫描。

8.5.2.2 How a Hash Scan Works

在散列簇中,具有相同散列值的所有行都存储在同一个数据块中。

为了对集群执行散列扫描,Oracle 数据库首先通过将散列函数应用于语句指定的集群键值来获取散列值。 Oracle 数据库然后扫描包含具有此哈希值的行的数据块。

8.5.2.3 Hash Scans: Example

此示例对部门 ID 列上的雇员和部门表进行哈希处理,然后在集群中查询单个部门。

您创建一个哈希集群和集群中的表,如下所示:

CREATE CLUSTER employees_departments_cluster(department_id NUMBER(4)) SIZE 8192 HASHKEYS 100;CREATE TABLE employees2CLUSTER employees_departments_cluster (department_id) AS SELECT * FROM employees;CREATE TABLE departments2 CLUSTER employees_departments_cluster (department_id) AS SELECT * FROM departments;

您查询部门 30 中的员工如下:

SELECT *
FROM   employees2
WHERE  department_id = 30

为了执行散列扫描,Oracle 数据库首先通过对键值 30 应用散列函数来获得散列值,然后使用该散列值扫描数据块并检索行(步骤 1)。

----------------------------------------------------------------
| Id  | Operation         | Name       | Rows  | Bytes | Cost  |
----------------------------------------------------------------
|   0 | SELECT STATEMENT  |            |       |       |     1 |
|*  1 |  TABLE ACCESS HASH| EMPLOYEES2 |    10 |  1330 |       |
----------------------------------------------------------------Predicate Information (identified by operation id):
---------------------------------------------------1 - access("DEPARTMENT_ID"=30)

http://chatgpt.dhexx.cn/article/KqZjVE8g.shtml

相关文章

2023年还在问前端怎么学?一份前端学习指南

&#x1f36d; Hello&#xff0c;我是爱吃糖的范同学 因为疫情摆烂了许久&#xff0c;学校里又有许多大大小小的烦心事要处理&#xff08;特别是作业和期末考试&#x1f62b;&#xff09;&#xff0c;所以在博客文章的更新上很长一段时间都没有输出很正经的文章了&#xff0c;…

【前端圭臬】一:写给入坑前端的你

专栏目录 导论 【前端圭臬】一&#xff1a;写给入坑前端的你 HTML 篇 【前端圭臬】二&#xff1a;你知道的和不知道的 HTML CSS 篇 【前端圭臬】三&#xff1a;详解 CSS 选择器 【前端圭臬】四&#xff1a;CSS 核心机制——如何确定一个属性的值&#xff1f; 【前端圭臬】…

【前端是什么】

前端是什么&#xff1f; 文章目录 前端是什么&#xff1f;1.前端的概念2. 前端的分类3. 前端的工作流程4. 互联网企业的典型组织结构图 1.前端的概念 1.1 前端的定义 对于网站来说&#xff0c;通常是指网站的前台部分&#xff0c;包括网站的表现层和结构层&#xff08;通俗点就…

前端什么知识点最难学?

这个最就很难判断&#xff0c;每个人的理解程度不一致&#xff0c;但有两种人&#xff0c;觉得难的他还没学会&#xff0c;会的不觉得难&#xff0c;但是前端不一样了&#xff0c;会了也难&#xff0c;因为谁都对前端提点建议&#xff08;啥也不懂的老板、产品经理、项目经理、…

【前端】一个好看的前端页面

序言 突发奇想&#xff0c;看到这个特效还不错&#xff0c;就加工了一下&#xff0c;如果也能帮到你&#xff0c;很开心 先上效果图 部分代码讲解 前端生成uuid function getUUID(len, radix) {var chars 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz.s…

从零学前端第一讲:前端开发是什么?给初学者有什么建议?

这里是修真院前端小课堂&#xff0c;本篇分析的主题是 【从零学前端第一讲&#xff1a;前端开发是什么&#xff1f;给初学者有什么建议&#xff1f;】 初识前端 1、前端开发是什么&#xff1f; 2、前端开发有前途吗&#xff1f; 3、前端开发哪里学&#xff1f; 4、前端发展…

前端是什么

此为小白浅显整合&#xff1a; 一直以来&#xff0c;对前端一直是一个笼统的概念&#xff0c;于是&#xff0c;专门花时间整合了网上各种说法&#xff0c;给自己一个较正确的定位。 前端是什么&#xff1f;&#xff1f; 前端&#xff0c;也称web前端。对于网站来说&#xff0…

怎么才能学好前端?

你如果知道简单的问题要百度&#xff0c;已经强过一部分人了。 至于学习方法&#xff0c;有些经验分享给你&#xff1a; 1.在学习的过程中&#xff0c;最好不是仅仅停留在前端与移动开发表层&#xff0c;不是抄书上的例子运行出结果就可以。要注意&#xff0c;即便对一个简单的…

web 前端入坑第一篇:web前端到底是什么?有前途吗

web前端到底是什么&#xff1f; 某货&#xff1a; “前几年前端开发人员鱼目混杂&#xff0c;技术参差不齐&#xff0c;相对学习起来不规范&#xff0c;导致> 前端开发人员聚集&#xff0c;所以现在前端工种和工资还是没得到普遍重视&#xff0c;但近2年来&#xff0c;> …

前端工程化详解——理解与实践前端工程化

前言&#xff1a; 前端工程化一直是一个老生常谈的问题&#xff0c;不管是面试还是我们在公司做基建都会经常提到前端工程化&#xff0c;那么为什么经常会说到前端工程化&#xff0c;并没有听过后端工程化、Java工程化或者Python工程化呢&#xff1f;我们理解的前端工程化是不是…

适合我的前端学习路线(学习前端不迷路)

适合我的前端学习路线&#xff08;学习前端不迷路&#xff09; 小伙伴们想学习前端&#xff0c;但是却不知如何入手&#xff0c;上网查前端学习路线&#xff0c;第一页往往充斥着各种培训公司的广告&#xff0c;又或者是搜前端学习路线图时&#xff0c;出现大量的路线图导致你还…

前端工程师行业现状怎么样?前景如何?

前端工程师行业现状怎么样&#xff1f;前景如何&#xff1f;前端即网站前台部分&#xff0c;运行在PC端移动端等浏览器上展现给用户浏览的网页。前端市场远没有饱和&#xff0c;现在的市场状况需求量非常多薪资可观。小编将通过官方数据宏观的给大家分析一下前端这个行业。 一…

快毕业了,很多人都不推荐做前端,前端业内最真实情况是什么样的?

偶尔出现这样一种说法,前端热度已经降低了,前端市场已经饱和了,前端工作没有那么好找了。 部分事实确实如此,大概是在2014-2018年,那时候前端技术刚刚崛起,相对来讲工作较现在更容易找到。 随着前端技术的发展及人才需求结构的成熟,现在已经不是会做动态网页就能找到工…

传智教育|一文读懂什么是前端?未来前景怎么样?(附前端学习路线图)

互联网高薪岗位众多&#xff0c;之前被大家津津乐道的后端工程师似乎慢慢不吃香了&#xff0c;原因是&#xff1a;过于内卷。 而为了打破目前内卷的局面&#xff0c;很多小伙伴在转行的时候就开始考虑起了前端工程师&#xff0c;这个岗位相比起后端的学习来说&#xff0c;难度…

我适不适合学前端?看完老程序员的这篇文章给你答案

什么是前端?什么又是后端呢? 1,在你机子上(PC端/手机端)看到的界面,安装的程序。给用户看的、操作的就是前端。你看不到的,帮你保存网络游戏数据,保存应用数据,处理数据的就是后端(服务端)。 2, 目前一般来说,前端指的就是浏览器端,后端指的就是直接为浏览器端…

关于根轨迹对于控制系统的一点理解

自动控制理论根轨迹的学习过程中&#xff0c;经常会遇到几个问题&#xff1a; 为什么要用根轨迹法&#xff1f;为什么根轨迹法最终转化为调整增益K来反应系统的稳定性和动态性能&#xff1f;为什么根轨迹法用开环传递函数求解的却是闭环极点&#xff1f; 盲目的借助于matlab进…

4.4 绘制根轨迹的MATLAB函数

4.4.1 rlocus ⒈.功能&#xff1a;绘制系统的根轨迹。 ⒉.格式&#xff1a; [r,k]rlocus(n,d) [r,k]rlocus(g) [r,k]rlocus(n,d,k) [r,k]rlocus(g,k) ⒊.说明&#xff1a; rlocus 函数可计算出或画出SISO系统的根轨迹&#xff0c;其中g(或n,d)为对象模型&#xff0c;输入变量…

自动控制原理复习——线性系统的根轨迹法

我们知道线性系统的性能与闭环函数的极点分布有很大关系&#xff0c;当闭环函数的极点全部位于s左平面时&#xff0c;系统是稳定的&#xff0c;当闭环函数的极点都是实数且无零点的情况下&#xff0c;系统单位阶跃响应曲线是单调的&#xff0c;当闭环函数的极点是共轭复数根时&…