SQL调优指南笔记6:Explaining and Displaying Execution Plans

Posted dingdingfish

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SQL调优指南笔记6:Explaining and Displaying Execution Plans相关的知识,希望对你有一定的参考价值。

本文为SQL Tuning Guide第6章“解释和显示执行计划”的笔记。

了解如何解释SQL语句并显示其计划对于 SQL 调优至关重要。

重要基本概念

  • row source tree
    A collection of row sources produced by the row source generator. The row source tree for a SQL statement shows information such as table order, access methods, join methods, and data operations such as filters and sorts.
    行源树
    由行源生成器生成的行源集合。 SQL 语句的行源树显示表顺序、访问方法、连接方法和数据操作(如过滤器和排序)等信息。

  • adaptive query plan
    An execution plan that changes after optimization because run-time conditions indicate that optimizer estimates are inaccurate. An adaptive query plan has different built-in plan options. During the first execution, before a specific subplan becomes active, the optimizer makes a final decision about which option to use. The optimizer bases its choice on observations made during the execution up to this point. Thus, an adaptive query plan enables the final plan for a statement to differ from the default plan.
    由于运行时条件表明优化器估计不准确而在优化后更改的执行计划。 自适应查询计划具有不同的内置计划选项。 在第一次执行期间,在特定子计划激活之前,优化器会最终决定使用哪个选项。 优化器的选择基于执行期间到当前为止的观察结果。 因此,自适应查询计划使语句的最终计划不同于默认计划。

  • final plan
    In an adaptive plan, the plan that executes to completion. The default plan can differ from the final plan.
    在自适应计划中,执行完成的计划。 默认计划可能与最终计划不同。

  • default plan
    For an adaptive plan, the execution plan initially chosen by the optimizer using the statistics from the data dictionary. The default plan can differ from the final plan.
    对于自适应计划,优化器最初使用数据字典中的统计信息选择的执行计划。 默认计划可能与最终计划不同。

  • automatic reoptimization
    The ability of the optimizer to automatically change a plan on subsequent executions of a SQL statement. Automatic reoptimization can fix any suboptimal plan chosen due to incorrect optimizer estimates, from a suboptimal distribution method to an incorrect choice of degree of parallelism.
    优化器在 SQL 语句的后续执行中自动更改计划的能力。 自动重新优化可以修复由于不正确的优化器估计而选择的任何次优计划,从次优分布方法到错误选择并行度。

6.1 Introduction to Execution Plans

执行计划是数据库为运行 SQL 语句而执行的操作序列。

6.1.1 Contents of an Execution Plan

单独的执行计划操作无法区分调整良好的语句和执行不理想的语句。

以下计划由一系列步骤组成。 每个步骤要么从数据库物理检索数据行,要么为发出语句的用户准备它们(数据行)。 以下计划显示了员工和部门表的连接:

SQL_ID  g9xaqjktdhbcd, child number 0
-------------------------------------
SELECT employee_id, last_name, first_name, department_name from
employees e, departments d WHERE e.department_id = d.department_id and
last_name like 'T%' ORDER BY last_name

Plan hash value: 1219589317

----------------------------------------------------------------------------------------
| Id | Operation                    | Name        |Rows | Bytes |Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------
|  0 | SELECT STATEMENT             |             |     |       |    5 (100)|          |
|  1 |  NESTED LOOPS                |             |   5 |   190 |    5   (0)| 00:00:01 |
|  2 |   TABLE ACCESS BY INDEX ROWID| EMPLOYEES   |   5 |   110 |    2   (0)| 00:00:01 |
|* 3 |    INDEX RANGE SCAN          | EMP_NAME_IX |   5 |       |    1   (0)| 00:00:01 |
|* 4 |   TABLE ACCESS FULL          | DEPARTMENTS |   1 |    16 |    1   (0)| 00:00:01 |
----------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   3 - access("LAST_NAME" LIKE 'T%')
       filter("LAST_NAME" LIKE 'T%')
   4 - filter("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")

行源树是执行计划的核心。 此树显示以下信息:

  • 语句引用的表的连接顺序
    在以上计划中,员工是外行源(外围表,outer table,因为位于上方),部门是内行源(inner table)。
  • 语句中提到的每个表的访问路径
    在以上计划中,优化器选择使用索引扫描访问员工,使用全表扫描访问部门。
  • 语句中受连接操作影响的表的连接方法
    在以上计划中,优化器选择了一个嵌套循环连接。
  • 过滤、排序或聚合等数据操作
    在以上计划中,优化器过滤以 T 开头并匹配 department_id 的姓氏。

除了行源树之外,计划表还包含有关以下内容的信息:

  • 优化,例如每个操作的成本和基数
  • 分区,例如访问的分区集
  • 并行执行,比如join输入的分布方式

6.1.2 Why Execution Plans Change

执行计划可以并且确实会随着底层优化器输入的变化而变化。

注意:为避免执行计划更改可能导致的 SQL 性能回归,请考虑使用 SQL 计划管理。

6.1.2.1 Different Schemas

由于各种原因,schema可能会有所不同。

主要原因包括以下几点:

  • 计划的执行和解释发生在不同的数据库上。
  • 解释语句的用户与运行语句的用户不同。 两个用户可能指向同一个数据库中的不同对象,从而导致不同的执行计划。
  • 两个操作之间的schema更改(通常是索引更改)。

6.1.2.2 Different Costs

即使模式相同,优化器也可以在成本不同时选择不同的执行计划。

影响成本的一些因素包括:

  • 数据量和统计信息
  • 绑定变量类型和值
  • 全局或会话级别设置的初始化参数

6.2 Generating Plan Output Using the EXPLAIN PLAN Statement

EXPLAIN PLAN 语句使您能够检查优化器为 SQL 语句选择的执行计划(但不是实际的执行计划)。

6.2.1 About the EXPLAIN PLAN Statement

EXPLAIN PLAN 语句显示优化器为 SELECT、UPDATE、INSERT 和 DELETE 语句选择的执行计划。

EXPLAIN PLAN 输出显示在解释语句时数据库将如何运行 SQL 语句。 由于执行环境和解释计划环境的不同,解释计划可能与语句执行期间使用的实际计划不同。

当 EXPLAIN PLAN 语句发出时,优化器选择一个执行计划,然后将描述执行计划每个步骤的行插入到指定的计划表中。 您还可以发出 EXPLAIN PLAN 语句作为 SQL 跟踪工具的一部分。

EXPLAIN PLAN 语句是 DML 语句而不是 DDL 语句。 因此,Oracle 数据库不会隐式提交 EXPLAIN PLAN 语句所做的更改。

6.2.1.1 About PLAN_TABLE

PLAN_TABLE 是 EXPLAIN PLAN 语句插入描述执行计划的行的默认示例输出表。

Oracle 数据库自动在 SYS 模式中创建一个全局临时表 PLAN_TABLE$,并创建 PLAN_TABLE 作为同义词。 PLAN_TABLE 的所有必要权限都授予 PUBLIC。 因此,每个会话都会在其临时表空间中获得自己的 PLAN_TABLE 私有副本。

您可以使用 SQL 脚本 catplan.sql 手动创建全局临时表和 PLAN_TABLE 同义词。 此脚本的名称和位置取决于您的操作系统。 在 UNIX 和 Linux 上,该脚本位于 $ORACLE_HOME/rdbms/admin 目录中。 例如,启动 SQL*Plus 会话,以 SYSDBA 权限连接,然后运行脚本,如下所示:

@$ORACLE_HOME/rdbms/admin/catplan.sql

示例输出表 PLAN_TABLE 的定义在您的分发媒体上的 SQL 脚本中可用。 您的输出表必须与该表具有相同的列名和数据类型。 此脚本的通用名称是 utlxplan.sql。 确切的名称和位置取决于您的操作系统。

6.2.1.2 EXPLAIN PLAN Restrictions

对于执行日期绑定变量的隐式类型转换的语句,Oracle 数据库不支持 EXPLAIN PLAN。

通常使用绑定变量,EXPLAIN PLAN 输出可能不代表真正的执行计划。

从 SQL 语句的文本中,TKPROF 无法确定绑定变量的类型。 它假定类型是 VARCHAR,否则给出错误消息。 您可以通过在 SQL 语句中进行适当的类型转换来避免此限制。

6.2.2 Explaining a SQL Statement: Basic Steps

使用 EXPLAIN PLAN 将 SQL 语句的计划存储在 PLAN_TABLE 中。

先决条件

此任务假定您的模式中存在一个名为 PLAN_TABLE 的示例输出表。 如果此表不存在,则运行 SQL 脚本 catplan.sql。

要执行 EXPLAIN PLAN,您必须具有以下权限:

  • 您必须具有将行插入到您指定用于保存执行计划的现有输出表中所需的权限
  • 您还必须具有执行要为其确定执行计划的 SQL 语句所需的权限。 如果 SQL 语句访问视图,则您必须具有访问该视图所基于的任何表和视图的权限。 如果视图基于另一基于表的视图,则您必须具有访问另一个视图及其所基于表的权限。

要检查 EXPLAIN PLAN 语句生成的执行计划,您必须具有查询输出表所需的权限。

EXPLAIN PLAN FOR <SQL 语句>;
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY(format => 'ALL')); -- format有很多设置

解释一个语句

EXPLAIN PLAN 输出中的执行顺序从向右缩进最远的行开始(大多数情况如此,但不一定)下一步执行的是该行的父级。 如果两行缩进相等,则通常首先执行上方的行。

注意:本章中 EXPLAIN PLAN 输出中的步骤可能与您的数据库不同。 优化器可能会根据数据库配置选择不同的执行计划。

6.2.3 Specifying a Statement ID in EXPLAIN PLAN: Example

对于多个语句,您可以指定一个语句标识符并使用它来标识您的特定执行计划。

在使用 SET STATEMENT ID 之前,删除该语句 ID 的所有现有行。 在以下示例中,st1 被指定为语句标识符。

EXPLAIN PLAN
  SET STATEMENT_ID = 'st1' FOR
  SELECT last_name FROM employees;

6.2.4 Specifying a Different Location for EXPLAIN PLAN Output: Example

EXPLAIN PLAN 的 INTO 子句指定了一个不同的表来存储输出。

如果您不想使用名称 PLAN_TABLE,请在运行 catplan.sql 脚本后创建一个新的同义词。 例如:

CREATE OR REPLACE PUBLIC SYNONYM my_plan_table for plan_table$

EXPLAIN PLAN
  INTO my_plan_table FOR
  SELECT last_name FROM employees;

EXPLAIN PLAN
   SET STATEMENT_ID = 'st1'
   INTO my_plan_table FOR
   SELECT last_name FROM employees;

6.2.5 EXPLAIN PLAN Output for a CONTAINERS Query: Example

CONTAINERS 子句可用于查询用户创建的和 Oracle 提供的表和视图。 它使您能够跨所有容器查询这些表和视图。

并且默认都会启用并行和分区。

6.3 Displaying Execution Plans

显示执行计划的最简单方法是使用 DBMS_XPLAN display函数或 V$ 视图。

6.3.1 About the Display of PLAN_TABLE Output

要显示计划表输出,您可以使用 SQL 脚本或 DBMS_XPLAN 包。

解释完计划后,使用 Oracle 数据库提供的以下 SQL 脚本或 PL/SQL 包显示最近的计划表输出:

  • DBMS_XPLAN.DISPLAY 表函数
  • utlxpls.sql
    此脚本显示串行处理的计划表输出
  • utlxplp.sql
    此脚本显示计划表输出,包括并行执行列。

以上3种方法的示例为:

explain plan for select count(*) from employees;
select * from table(dbms_xplan.display);
-- or
@?/rdbms/admin/utlxpls
-- or
@?/rdbms/admin/utlxplp

6.3.1.1 DBMS_XPLAN Display Functions

您可以使用 DBMS_XPLAN 显示函数来显示计划。

显示函数接受用于显示计划表输出的选项。 您可以指定:

  • 计划表名称,如果您使用不同于 PLAN_TABLE 的表
  • 语句 ID,如果您使用 EXPLAIN PLAN 设置了语句 ID
  • 确定详细程度的格式选项:BASIC、SERIAL、TYPICAL、ALL,在某些情况下为 ADAPTIVE

DBMS_XPLAN 显示函数如下表:

显示函数说明
DISPLAY此表函数显示计划表的内容。
此外,您可以使用此表函数显示存储在表中的任何计划(有或没有统计信息),只要此表的列与计划表的列命名相同(如果包含统计信息,则为 V$SQL_PLAN_STATISTICS_ALL )。 您可以在指定的表上应用谓词来选择要显示的计划行。
格式参数控制计划的级别。 它接受值 BASIC、TYPICAL、SERIAL 和 ALL。
DISPLAY_AWR此表函数显示存储在 AWR 中的执行计划的内容。格式参数同上。
DISPLAY_CURSOR此表函数显示游标缓存中加载的任何游标的解释计划。 除了解释计划之外,还可以报告各种计划统计信息(如 I/O、内存和计时)(基于 V$SQL_PLAN_STATISTICS_ALL VIEWS)。

格式参数控制计划的级别。 它接受值 BASIC、TYPICAL、SERIAL、ALL 和 ADAPTIVE。 当您指定 ADAPTIVE 时,输出包括:
- 最后的计划。 如果执行尚未完成,则输出显示当前计划。 本节还包括有关影响计划的运行时优化的注释。
- 推荐计划。 在报告模式下,输出包括将根据执行统计信息选择的计划。
- 动态计划。 输出总结了计划中与优化器选择的默认计划不同的部分。
- 重新优化。 输出显示由于重新优化而将在后续执行中选择的计划。
DISPLAY_PLAN此表函数以各种格式显示计划表的内容,具有 CLOB 输出类型。

格式参数控制计划的级别。 它接受值 BASIC、TYPICAL、SERIAL、ALL 和 ADAPTIVE。 当您指定 ADAPTIVE 时,输出包括默认计划。 对于每个动态子计划,该计划显示原始行源中可能被替换的行源的列表,以及将替换它们的行源。

如果格式参数指定大纲显示,则函数显示动态子计划中每个选项的提示。 如果计划不是自适应查询计划,则该函数显示默认计划。 当您未指定 ADAPTIVE 时,计划按原样显示,但在注释部分中显示任何动态行源的附加注释。
DISPLAY_SQL_PLAN_BASELINE此表函数显示 SQL 计划基线的指定 SQL 句柄的一个或多个执行计划。

此功能使用存储在计划基线中的计划信息来解释和显示计划。 SQL管理库中存储的plan_id可能与生成计划的plan_id不匹配。 存储的 plan_id 和生成的 plan_id 不匹配意味着它是一个不可重现的计划。 这样的计划被认为是无效的,并在 SQL 编译期间被优化器绕过。
DISPLAY_SQLSET此表函数显示存储在 SQL 调整集中的给定语句的执行计划。

格式参数控制计划的级别。 它接受值 BASIC、TYPICAL、SERIAL 和 ALL。

6.3.1.2 Plan-Related Views

您可以通过查询动态性能和数据字典视图来获取有关执行计划的信息。

  • V$SQL
    列出游标的统计信息,并为输入的原始 SQL 文本的每个子项包含一行。

    从 Oracle Database 19c 开始,V$SQL.QUARANTINED 指示语句是否已被资源管理器终止,因为该语句消耗了太多资源。 Oracle 数据库记录并标记隔离的计划,并阻止执行使用这些计划的语句执行。 AVOIDED_EXECUTIONS 列指示由于隔离语句而尝试但被阻止的执行次数。

  • V$SQL_SHARED_CURSOR
    解释为什么特定的子游标不与现有的子游标共享。 每列标识不能共享游标的特定原因。

    USE_FEEDBACK_STATS 列显示子游标是否由于重新优化而无法匹配。

  • V$SQL_PLAN
    包含存储在共享 SQL 区域中的每个语句的计划。

    视图定义类似于 PLAN_TABLE。 该视图包括出现在所有最终计划中的所有行的超集。 PLAN_LINE_ID 是连续编号的,但对于单个最终计划,ID 可能不连续。

    作为 EXPLAIN PLAN 的替代方法,您可以通过查询 V$SQL_PLAN 来显示计划。 V$SQL_PLAN 优于 EXPLAIN PLAN 的优点是您不需要知道用于执行特定语句的编译环境。 对于 EXPLAIN PLAN,您需要设置相同的环境以在执行语句时获得相同的计划。

  • V$SQL_PLAN_STATISTICS
    提供计划中每个操作的实际执行统计信息,例如输出行数和经过的时间。 除输出行数外,所有统计信息都是累计的。 例如,联结操作的统计信息还包括其两个输入的统计信息。 V$SQL_PLAN_STATISTICS 中的统计信息可用于在 STATISTICS_LEVEL 初始化参数设置为 ALL 的情况下编译的游标。

  • V$SQL_PLAN_STATISTICS_ALL
    包含使用 SQL 内存(排序或散列连接)的行源的内存使用统计信息。 此视图将 V$SQL_PLAN 中的信息与来自 V$SQL_PLAN_STATISTICSV$SQL_WORKAREA 的执行统计信息连接起来。

    V$SQL_PLAN_STATISTICS_ALL 允许并排比较优化器为行数和经过时间提供的估计值。 这个视图结合了每个游标的 V$SQL_PLANV$SQL_PLAN_STATISTICS 信息。

6.3.2 Displaying Execution Plans: Basic Steps

DBMS_XPLAN.DISPLAY 函数是一种显示解释计划的简单方法。

默认情况下,DISPLAY 功能使用 TYPICAL 格式设置。 在这种情况下,计划与计划中最相关的信息:操作id、名称和选项、行、字节和优化器成本。 修剪、并行和谓词信息仅在适用时显示。

例如:

-- 例1
explain plan 
	for <SQL>;
select * from table(dbms_xplan.display);

-- 例2
explain plan 
  SET statement_id = 'ex_plan2'
  for <SQL>;;

SELECT * from from table(DBMS_XPLAN.DISPLAY(NULL, 'ex_plan2','BASIC'));

6.3.3 Displaying Adaptive Query Plans: Tutorial

自适应优化器是优化器的一项功能,它可以根据运行时统计信息调整计划。 所有自适应机制都可以为语句执行不同于默认计划的最终计划。

自适应查询计划在当前语句执行期间对子计划进行选择。 相反,自动重新优化仅在当前语句执行之后发生的执行上更改计划。

您可以根据计划的Notes中的注释来确定数据库是否对 SQL 语句使用了自适应查询优化。 注释表明行源是否是动态的,或者自动重新优化是否适应了计划。

假设

本教程假定以下内容:

  • STATISTICS_LEVEL 初始化参数设置为 ALL。
-- 默认值为TYPICAL
-- 也可以设置gather_plan_statistics hint
alter system set STATISTICS_LEVEL = 'ALL';
  • 数据库使用默认设置进行自适应执行。
  • 作为用户 oe,您要发出以下单独的查询:
-- 查询1
SELECT o.order_id, v.product_name
FROM   orders o,
       (  SELECT order_id, product_name
          FROM   order_items o, product_information p
          WHERE  p.product_id = o.product_id
          AND    list_price < 50
          AND    min_price < 40  ) v
WHERE  o.order_id = v.order_id;

-- 查询2
SELECT product_name
FROM   order_items o, product_information p  
WHERE  o.unit_price = 15 
AND    quantity > 1
AND    p.product_id = o.product_id;
  • 在执行每个查询之前,您希望查询 DBMS_XPLAN.DISPLAY_PLAN 以查看默认计划,即优化器在应用其自适应机制之前选择的计划。
  • 执行每个查询后,您要查询 DBMS_XPLAN.DISPLAY_CURSOR 以查看最终计划和自适应查询计划。
  • SYS 已授予 oe 下列权限:
GRANT SELECT ON V_$SESSION TO oe;
GRANT SELECT ON V_$SQL TO oe;
GRANT SELECT ON V_$SQL_PLAN TO oe;
GRANT SELECT ON V_$SQL_PLAN_STATISTICS_ALL TO oe;

要查看自适应优化的结果:

  1. 以用户oe连接数据库
  2. 执行查询1(第1次)
  3. 查询游标中的执行计划,此处使用了MERGE JOIN,E-Rows和A-Rows差距很大。
SET LINESIZE 165
SET PAGESIZE 0
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(FORMAT=>'+ALLSTATS'));

Plan hash value: 1906736282
 
----------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation             | Name                | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  OMem |  1Mem |  O/1/M   |
----------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |                     |      1 |        |    269 |00:00:00.01 |    1290 |       |       |          |
|   1 |  NESTED LOOPS         |                     |      1 |      1 |    269 |00:00:00.01 |    1290 |       |       |          |
|   2 |   MERGE JOIN CARTESIAN|                     |      1 |      4 |   9135 |00:00:00.01 |      17 |       |       |          |
|*  3 |    TABLE ACCESS FULL  | PRODUCT_INFORMATION |      1 |      1 |     87 |00:00:00.01 |      16 |       |       |          |
|   4 |    BUFFER SORT        |                     |     87 |    105 |   9135 |00:00:00.01 |       1 |  4096 |  4096 |     1/0/0|
|   5 |     INDEX FULL SCAN   | ORDER_PK            |      1 |    105 |    105 |00:00:00.01 |       1 |       |       |          |
|*  6 |   INDEX UNIQUE SCAN   | ORDER_ITEMS_UK      |   9135 |      1 |    269 |00:00:00.01 |    1273 |       |       |          |
----------------------------------------------------------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   3 - filter(("MIN_PRICE"<40 AND "LIST_PRICE"<50))
   6 - access("O"."ORDER_ID"="ORDER_ID" AND "P"."PRODUCT_ID"="O"."PRODUCT_ID")
 

28 rows selected. 
  1. 第2次执行此查询
  2. 再次查询执行计划,这回变成了HASH JOIN,E-Rows和A-Rows比较接近,注意Notes中的信息,正是因为使用了statistics feedback,才使执行计划更优
SQL_ID  gm2npz344xqn8, child number 1
------------------------------------- 
Plan hash value: 35479787
 
-----------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation              | Name                | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  OMem |  1Mem |  O/1/M   |
-----------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT       |                     |      1 |        |    269 |00:00:00.01 |      29 |       |       |          |
|   1 |  NESTED LOOPS          |                     |      1 |    313 |    269 |00:00:00.01 |      29 |       |       |          |
|*  2 |   HASH JOIN            |                     |      1 |    313 |    269 |00:00:00.01 |      24 |  1399K|  1399K|     1/0/0|
|*  3 |    TABLE ACCESS FULL   | PRODUCT_INFORMATION |      1 |     87 |     87 |00:00:00.01 |      15 |       |       |          |
|   4 |    INDEX FAST FULL SCAN| ORDER_ITEMS_UK      |      1 |    665 |    665 |00:00:00.01 |       9 |       |       |          |
|*  5 |   INDEX UNIQUE SCAN    | ORDER_PK            |    269 |      1 |    269 |00:00:00.01 |       5 |       |       |          |
-----------------------------------------------------------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   2 - access("P"."PRODUCT_ID"="O"."PRODUCT_ID")
   3 - filter(("MIN_PRICE"<40 AND "LIST_PRICE"<50))
   5 - access("O"."ORDER_ID"="ORDER_ID")
 
Note
-----
   - statistics feedback used for this statement
 

32 rows selected. 
  1. 查询V$SQL确认性能提升
SELECT CHILD_NUMBER, CPU_TIME, ELAPSED_TIME, BUFFER_GETS
FROM   V$SQL
WHERE  SQL_ID = 'gm2npz344xqn8';

CHILD_NUMBER   CPU_TIME ELAPSED_TIME BUFFER_GETS
------------ ---------- ------------ -----------
           0      33042        35734        1563
           1       4967         7182          43
  1. 解释查询2的计划
EXPLAIN PLAN FOR
  SELECT product_name 
  FROM   order_items o, product_information p  
  WHERE  o.unit_price = 15
  AND    quantity > 1  
  AND    p.product_id = o.product_id
  1. 查看计划表中的计划。从Note部分可知,这是一个adaptive plan。
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);

Plan hash value: 1255158658
 
-------------------------------------------------------------------------------------------------------
| Id  | Operation                    | Name                   | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |                        |     4 |   128 |     7   (0)| 00:00:01 |
|   1 |  NESTED LOOPS                |                        |     4 |   128 |     7   (0)| 00:00:01 |
|   2 |   NESTED LOOPS               |                        |     4 |   128 |     7   (0)| 00:00:01 |
|*  3 |    TABLE ACCESS FULL         | ORDER_ITEMS            |     4 |    48 |     3   (0)| 00:00:01 |
|*  4 |    INDEX UNIQUE SCAN         | PRODUCT_INFORMATION_PK |     1 |       |     0   (0)| 00:00:01 |
|   5 |   TABLE ACCESS BY INDEX ROWID| PRODUCT_INFORMATION    |     1 |    20 |     1   (0)| 00:00:01 以上是关于SQL调优指南笔记6:Explaining and Displaying Execution Plans的主要内容,如果未能解决你的问题,请参考以下文章

SQL调优指南笔记17:Importing and Exporting Optimizer Statistics

SQL调优指南笔记17:Importing and Exporting Optimizer Statistics

EXPLAINING AND HARNESSING ADVERSARIAL EXAMPLES 论文笔记

SQL调优指南笔记9:Joins

SQL调优指南笔记9:Joins

SQL调优指南笔记1:Introduction to SQL Tuning