SQL调优指南笔记9:Joins

Posted dingdingfish

tags:

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

本文为SQL Tuning Guide第9章“Join”的笔记。

重要基本概念

  • join condition
    A condition that compares two row sources using an expression. The database combines pairs of rows, each containing one row from each row source, for which the join condition evaluates to true.
    加入条件
    使用表达式比较两个行源的条件。 数据库组合成对的行,每个行包含来自每个行源的一行,对于这些行,连接条件的计算结果为真。

  • left deep join tree
    A join tree in which the left input of every join is the result of a previous join.
    一个连接树,其中每个连接的左输入是前一个连接的结果。

  • right deep join tree
    A join tree in which the right input of every join is the result of a previous join, and the left child of every internal node of a join tree is a table.
    一个连接树,其中每个连接的右输入是前一个连接的结果,连接树的每个内部节点的左子节点是一个表。

  • left table
    In an outer join, the table specified on the left side of the OUTER JOIN keywords (in ANSI SQL syntax).
    在外连接中,在 OUTER JOIN 关键字左侧指定的表(在 ANSI SQL 语法中)。

  • right table
    In an outer join, the table specified on the right side of the OUTER JOIN keywords (in ANSI SQL syntax).
    在外连接中,在 OUTER JOIN 关键字右侧指定的表(在 ANSI SQL 语法中)。

Oracle 数据库为联结行集提供了多种优化。

  • partition-wise join
    翻译为智能分区联结

9.1 About Joins

联结将恰好来自两个行源(例如表或视图)的输出组合在一起,并返回一个行源。 返回的行源是数据集。

联结的特征在于 SQL 语句的 WHERE(非 ANSI)或 FROM … JOIN (ANSI) 子句中的多个表。 只要 FROM 子句中存在多个表,Oracle 数据库就会执行联结。

联结条件使用表达式比较两个行源。 联结条件定义了表之间的关系。 如果语句未指定联结条件,则数据库执行笛卡尔联结,将一个表中的每一行与另一个表中的每一行匹配。

9.1.1 Join Trees

通常,连接树表示为倒置的树结构。

如下图所示,table1 为左表,table2 为右表。 优化器从左到右处理连接。 例如,如果此图描绘了一个嵌套循环连接,则 table1 是外循环,而 table2 是内循环。

连接的输入可以是先前连接的结果集。 如果连接树的每个内部节点的右子节点都是一个表,那么该树就是一个左深度连接树,如下例所示。 大多数连接树都是左深连接。

如果连接树的每个内部节点的左子节点都是一张表,则该树称为右深连接树,如下图所示。

如果连接树的内部节点的左或右子节点可以是连接节点,则该树称为浓密连接树。 在下面的示例中,table4 是连接节点的右子节点,table1 是连接节点的左子节点,table2 是连接节点的左子节点。

在另一个变体中,连接的两个输入都是先前连接的结果。

9.1.2 How the Optimizer Executes Join Statements

数据库连接成对的行源。 当 FROM 子句中存在多个表时,优化器必须确定对每对最有效的连接操作。

优化器必须做出下表所示的相关决策。

  • 访问路径
    对于简单语句,优化器必须选择一个访问路径来从连接语句中的每个表中检索数据。 例如,优化器可能会在全表扫描或索引扫描之间进行选择。
  • 联结方法
    要联结每对行源,Oracle 数据库必须决定如何进行。 “如何”即连接方法。 可能的连接方法是嵌套循环、排序合并和散列连接。 笛卡尔连接需要上述连接方法之一。 每种连接方法都有比其他方法更适合的特定情况。
  • 联结类型
    联结条件决定联结类型。 例如,内连接只检索与连接条件匹配的行。 外连接检索与连接条件不匹配的行。
  • 联结顺序
    为了执行联结两个以上表的语句,Oracle 数据库连接两个表,然后将生成的行源连接到下一个表。 这个过程一直持续到所有表都加入到结果中。 例如,数据库连接两个表,然后将结果连接到第三个表,然后将此结果连接到第四个表,依此类推。

9.1.3 How the Optimizer Chooses Execution Plans for Joins

在确定连接顺序和方法时,优化器的目标是尽早减少行数,以便在整个 SQL 语句执行过程中执行更少的工作。

优化器根据可能的连接顺序、连接方法和可用的访问路径生成一组执行计划。 然后优化器估计每个计划的成本并选择成本最低的一个。 在选择执行计划时,优化器会考虑以下因素:

  • 优化器首先确定连接两个或多个表是否会导致一个最多包含一行的行源。
    优化器根据表上的 UNIQUE 和 PRIMARY KEY 约束识别这种情况。如果存在这种情况,那么优化器会将这些表放在连接顺序的首位。优化器然后优化其余表集的连接。

  • 对于具有外连接条件的连接语句,具有外连接运算符的表通常在连接顺序中位于条件中的另一个表之后。
    通常,优化器不会考虑违反此准则的连接顺序,尽管优化器在某些情况下会覆盖此排序条件。同样,当子查询已转换为反连接或半连接时,来自子查询的表必须位于与它们连接或相关的外部查询块中的那些表之后。但是,哈希反连接和半连接能够在某些情况下覆盖此排序条件。

优化器通过计算估计的 I/O 和 CPU 来估计查询计划的成本。 这些 I/O 具有与之相关的特定成本:单个块 I/O 的成本和多块 I/O 的另一个成本。 此外,不同的函数和表达式具有与之相关的 CPU 成本。 优化器使用这些指标确定查询计划的总成本。 这些指标可能会受到编译时许多初始化参数和会话设置的影响,例如 DB_FILE_MULTI_BLOCK_READ_COUNT 设置、系统统计信息等。

例如,优化器通过以下方式估算成本:

  • 嵌套循环连接的成本取决于将外部表的每个选定行和内部表的每个匹配行读入内存的成本。 优化器使用数据字典中的统计信息来估计这些成本。
  • 排序合并连接的成本很大程度上取决于将所有源读入内存并对其进行排序的成本。
  • 哈希连接的成本很大程度上取决于在连接的一个输入端构建哈希表并使用来自连接另一端的行来探测它的成本。

从概念上讲,优化器构建了一个连接顺序和方法的矩阵以及与每个相关的成本。 例如,优化器必须确定在查询中如何最好地连接 date_dim 和 lineorder 表。 下表显示了方法和订单的可能变化,以及每个的成本。 在此示例中,以 date_dim,lineorder 顺序连接 的嵌套循环成本最低。

9.2 Join Methods

连接方法是连接两个行源的机制。

根据统计数据,优化器选择估计成本最低的方法。 如图 9-5 所示,每个连接方法都有两个孩子:驱动(也称为外部)行源和驱动目标(也称为内部)行源。

9.2.1 Nested Loops Joins

嵌套循环将外部数据集连接到内部数据集。

对于外部数据集中与单表谓词匹配的每一行,数据库检索内部数据集中满足连接谓词的所有行。 如果索引可用,那么数据库可以使用它来访问由 rowid 设置的内部数据。

9.2.1.1 When the Optimizer Considers Nested Loops Joins

当数据库连接小数据子集、数据库连接大量数据且优化器模式设置为 FIRST_ROWS,或连接条件存在访问内部表的有效方法时(如内表由索引),嵌套循环连接非常有用。

注意:连接预期的行数是优化器决策的驱动因素,而不是基础表的大小。 例如,一个查询可能连接两个表,每个表有 10 亿行,但由于过滤器,优化器期望每个数据集有 5 行。

一般来说,嵌套循环连接在连接条件上有索引的小表上效果最好。 如果行源只有一行,例如在主键值上进行相等查找(例如,WHERE employee_id=101),则连接是一个简单的查找。 优化器总是尝试将最小的行源放在第一位,使其成为驱动表。

各种因素进入优化器决定使用嵌套循环。例如,数据库可以批量从外部行源中读取几行。根据检索到的行数,优化器可以选择嵌套循环或哈希连接到内部行源。例如,如果查询将部门连接到驱动表employees,并且如果谓词指定employees.last_name 中的值,则数据库可能会读取last_name 索引中的足够条目以确定是否超过内部阈值。如果未通过阈值,则优化器选择对部门进行嵌套循环连接,如果通过阈值,则数据库执行散列连接,这意味着读取其余员工,将其散列到内存中,然后加入到部门。(这其实就是adaptive plan)

如果内循环的访问路径不依赖于外循环,则结果可以是笛卡尔积:对于外循环的每次迭代,内循环都会产生相同的行集。为避免此问题,请使用其他连接方法连接两个独立的行源。

9.2.1.2 How Nested Loops Joins Work

从概念上讲,嵌套循环相当于两个嵌套的 for 循环。

例如,如果查询连接员工和部门,则伪代码中的嵌套循环可能是:

FOR erow IN (select * from employees where X=Y) LOOP
  FOR drow IN (select * from departments where erow is matched) LOOP
    output values from erow and drow
  END LOOP
END LOOP

对外循环的每一行执行内循环。 employees 表是“外部”数据集,因为它位于外部 for 循环中。 外部表有时称为驱动表。 部门表是“内部”数据集,因为它位于内部 for 循环中。

嵌套循环连接涉及以下基本步骤:

  1. 优化器确定驱动行源并将其指定为外循环。
    外循环产生一组用于驱动连接条件的行。 行源可以是使用索引扫描、全表扫描或任何其他生成行的操作访问的表。
    内循环的迭代次数取决于在外循环中检索到的行数。 例如,如果从外部表中检索到 10 行,那么数据库必须在内部表中执行 10 次查找。 如果从外部表中检索到 10,000,000 行,则数据库必须在内表中执行 10,000,000 次查找。
  2. 优化器将另一个行源指定为内部循环。
    在执行计划中,外循环出现在内循环之前,如下:
NESTED LOOPS 
  outer_loop
  inner_loop 
  1. 对于客户端的每一个 fetch 请求,基本流程如下:
    a. 从外部行源获取一行
    b. 探测内部行源以查找与谓词条件匹配的行
    c. 重复上述步骤,直到 fetch 请求获取到所有行

有时,数据库对 rowid 进行排序以获得更有效的缓冲区访问模式。

9.2.1.3 Nested Nested Loops

嵌套循环的外循环本身可以是由不同的嵌套循环生成的行源。

数据库可以嵌套两个或多个外部循环,以根据需要连接尽可能多的表。 每个循环都是一种数据访问方法。 以下模板显示了数据库如何迭代三个嵌套循环:

SELECT STATEMENT
  NESTED LOOPS 3
    NESTED LOOPS 2          - Row source becomes OUTER LOOP 3.1
      NESTED LOOPS 1        - Row source becomes OUTER LOOP 2.1
        OUTER LOOP 1.1
        INNER LOOP 1.2  
      INNER LOOP 2.2
    INNER LOOP 3.2

例如:

set pages 9999
SELECT /*+ ORDERED USE_NL(d) */ e.last_name, e.first_name, d.department_name
FROM   employees e, departments d
WHERE  e.department_id=d.department_id
AND    e.last_name like 'A%';

select * from table(dbms_xplan.display_cursor);

-----------------------------------------------------------------------------------------------------
| Id  | Operation                             | Name        | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                      |             |       |       |     5 (100)|          |
|   1 |  NESTED LOOPS                         |             |     3 |   102 |     5   (0)| 00:00:01 |
|   2 |   NESTED LOOPS                        |             |     3 |   102 |     5   (0)| 00:00:01 |
|   3 |    TABLE ACCESS BY INDEX ROWID BATCHED| EMPLOYEES   |     3 |    54 |     2   (0)| 00:00:01 |
|*  4 |     INDEX RANGE SCAN                  | EMP_NAME_IX |     3 |       |     1   (0)| 00:00:01 |
|*  5 |    INDEX UNIQUE SCAN                  | DEPT_ID_PK  |     1 |       |     0   (0)|          |
|   6 |   TABLE ACCESS BY INDEX ROWID         | DEPARTMENTS |     1 |    16 |     1   (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   4 - access("E"."LAST_NAME" LIKE 'A%')
       filter("E"."LAST_NAME" LIKE 'A%')
   5 - access("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")

原文中这个例子讲的很详细,此不赘述。

9.2.1.4 Current Implementation for Nested Loops Joins

Oracle 数据库 11g 引入了嵌套循环的新实现,可减少物理 I/O 的总体延迟。

当索引或表块不在缓冲区高速缓存中并且需要处理连接时,需要物理 I/O。 数据库可以批量处理多个物理 I/O 请求,并使用向量 I/O(数组)而不是一次处理一个。 数据库将一组 rowids 发送到执行读取的操作系统。

9.2.1.5 Original Implementation for Nested Loops Joins

虽然很详细,但没太看懂两者之间的区别。

9.2.1.6 Nested Loops Controls

您可以添加 USE_NL 提示以指示优化器将每个指定的表连接到具有嵌套循环连接的另一个行源,使用指定的表作为内表。

相关提示 USE_NL_WITH_INDEX(table index) 提示指示优化器使用指定表作为内表,通过嵌套循环连接将指定表连接到另一个行源。 该索引是可选的。 如果未指定索引,则嵌套循环连接使用具有至少一个连接谓词的索引作为索引键。

要强制使用部门作为内部表进行嵌套循环连接,请添加 USE_NL 提示,如以下查询中所示:

-- The ORDERED hint causes Oracle to join tables in the order in which they appear in the FROM clause.

SELECT /*+ ORDERED USE_NL(d) */ e.last_name, d.department_name
FROM   employees e, departments d
WHERE  e.department_id=d.department_id;

9.2.2 Hash Joins

数据库使用散列连接来连接更大的数据集。

优化器使用两个数据集中较小的一个在内存中的连接键上构建哈希表,使用确定性哈希函数指定哈希表中存储每一行的位置。 然后数据库扫描更大的数据集,探测哈希表以找到满足连接条件的行。

9.2.2.1 When the Optimizer Considers Hash Joins

一般来说,当需要连接比较大量的数据(或者小表的很大一部分需要连接)时,优化器会考虑使用哈希连接,这种连接是等值连接。

当较小的数据集适合内存时,哈希连接最具成本效益。 在这种情况下,成本仅限于对两个数据集进行一次读取。

因为哈希表在 PGA 中,所以 Oracle 数据库可以访问行而不锁定它们。 该技术通过避免在数据库缓冲区高速缓存中重复锁定和读取块的必要性来减少逻辑 I/O。

如果数据集不适合内存,那么数据库会对行源进行分区,并且连接会逐个分区进行。 这可以使用大量的排序区内存和临时表空间的 I/O。 这种方法仍然是最具成本效益的方法,尤其是当数据库使用并行查询服务器时。

9.2.2.2 How Hash Joins Work

散列算法采用一组输入并应用确定性散列函数来生成随机散列值。

在散列连接中,输入值是连接键。 输出值是数组中的索引(槽),它是哈希表。

9.2.2.2.1 Hash Tables

为了说明哈希表,假设数据库在部门和员工的联接中对 hr.departments 进行哈希处理。 连接键列是 department_id。

部门表前5行如下:

SQL> select * from departments where rownum < 6;
 
DEPARTMENT_ID DEPARTMENT_NAME                MANAGER_ID LOCATION_ID
------------- ------------------------------ ---------- -----------
           10 Administration                        200        1700
           20 Marketing                             201        1800
           30 Purchasing                            114        1700
           40 Human Resources                       203        2400
           50 Shipping                              121        1500

数据库将散列函数应用于表中的每个部门 ID,为每个部门生成一个散列值。 对于这个例子,哈希表有 5 个槽(它可能有更多或更少)。 因为 n 是 5,所以可能的散列值范围是 1 到 5。散列函数可能会为部门 ID 生成以下值:

f(10) = 4
f(20) = 1
f(30) = 4
f(40) = 2
f(50) = 5

请注意,散列函数恰好为部门 10 和 30 生成相同的散列值 4。这称为散列冲突。 在这种情况下,数据库使用链表将部门 10 和 30 的记录放在同一个槽中。 从概念上讲,哈希表如下所示:

1    20,Marketing,201,1800
2    40,Human Resources,203,2400
3
4    10,Administration,200,1700 -> 30,Purchasing,114,1700
5    50,Shipping,121,1500

从以上结果可知,即使哈希冲突也没有关系,因为还存放了额外的值。

9.2.2.2.2 Hash Join: Basic Steps

优化器使用较小的数据源在内存中的连接键上构建哈希表,然后扫描较大的表以查找连接的行。

基本步骤如下:

  1. 数据库对较小的数据集(称为构建表)执行完整扫描,然后将哈希函数应用于每行中的连接键,以在 PGA 中构建哈希表。
    在伪代码中,算法可能如下所示:
FOR small_table_row IN (SELECT * FROM small_table)
LOOP
  slot_number := HASH(small_table_row.join_key);
  INSERT_HASH_TABLE(slot_number,small_table_row);
END LOOP;
  1. 数据库使用成本最低的访问机制来探测第二个数据集,称为探测表。
    通常,数据库对较小和较大的数据集执行全表扫描。 伪代码中的算法可能如下所示:
FOR large_table_row IN (SELECT * FROM large_table)
LOOP
   slot_number := HASH(large_table_row.join_key);
   small_table_row = LOOKUP_HASH_TABLE(slot_number,large_table_row.join_key);
   IF small_table_row FOUND
   THEN
      output small_table_row + large_table_row;
   END IF;
END LOOP;

如果哈希表槽中存在多行,则数据库遍历行的链表,检查每一行。 例如,如果部门 30 散列到插槽 4,则数据库检查每一行,直到找到 30。

例如:

SELECT o.customer_id, l.unit_price * l.quantity
FROM   orders o, order_items l
WHERE  l.order_id = o.order_id;

--------------------------------------------------------------------------
| Id  | Operation            |  Name        | Rows  | Bytes | Cost (%CPU)|
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |              |   665 | 13300 |     8  (25)|
|*  1 |  HASH JOIN           |              |   665 | 13300 |     8  (25)|
|   2 |   TABLE ACCESS FULL  | ORDERS       |   105 |   840 |     4  (25)|
|   3 |   TABLE ACCESS FULL  | ORDER_ITEMS  |   665 |  7980 |     4  (25)|
--------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   1 - access("L"."ORDER_ID"="O"."ORDER_ID")

因为相对于大 6 倍的 order_items 表,orders 表很小,所以数据库对订单进行哈希处理。 在散列连接中,构建表的数据集始终出现在操作列表的首位(步骤 2)。 在步骤 3 中,数据库稍后对较大的 order_items 执行完整扫描,探测每一行的哈希表。

9.2.2.3 How Hash Joins Work When the Hash Table Does Not Fit in the PGA

当散列表不完全适合 PGA 时,数据库必须使用不同的技术。 在这种情况下,数据库使用一个临时空间来保存散列表的部分(称为分区),有时还包含探查散列表的较大表的部分。

基本流程如下:

  1. 数据库对较小的数据集执行完整扫描,然后在 PGA 和磁盘上构建散列桶数组。
    当 PGA 哈希区填满时,数据库会在哈希表中找到最大的分区并将其写入磁盘上的临时空间。 数据库将属于该磁盘分区的任何新行存储在磁盘上,并将所有其他行存储在 PGA 中。 因此,哈希表的一部分在内存中,一部分在磁盘上。
  2. 数据库开始第一轮读取其他数据集。
  3. 数据库一一读取每个磁盘上的临时分区
  4. 数据库将每个分区行连接到相应的磁盘临时分区中的行。

9.2.2.4 Hash Join Controls

USE_HASH 提示指示优化器在将两个表连接在一起时使用哈希连接。

9.2.3 Sort Merge Joins

排序合并连接是嵌套循环连接的一种变体。

如果连接中的两个数据集尚未排序,则数据库对它们进行排序。 这些是 SORT JOIN 操作。 对于第一个数据集中的每一行,数据库探测第二个数据集的匹配行并将它们连接起来,它的起始位置基于前一次迭代中的匹配。 这是 MERGE JOIN 操作。

9.2.3.1 When the Optimizer Considers Sort Merge Joins

哈希连接需要一个哈希表和一个对该表的探测,而排序合并连接需要两种排序。

当满足以下任一条件时,优化器可能会选择排序合并连接而不是哈希连接来连接大量数据:

  • 两个表之间的连接条件不是等值连接,即使用<、<=、>、>=等不等条件。
    与排序合并相比,散列连接需要相等条件。

  • 由于其他操作需要排序,优化器发现使用排序合并更便宜。
    如果存在索引,则数据库可以避免对第一个数据集进行排序。 但是,无论索引如何,数据库总是对第二个数据集进行排序(为什么?)

与嵌套循环连接相比,排序合并具有与哈希连接相同的优势:数据库访问 PGA 中的行而不是 SGA,通过避免重复锁定和读取数据库缓冲区缓存中的块的必要性来减少逻辑 I/O。 一般来说,哈希连接比排序合并连接执行得更好,因为排序很昂贵。 但是,排序合并连接比散列连接提供以下优点:

  • 在初始排序之后,优化合并阶段,从而更快地生成输出行。
  • 当哈希表不能完全置入内存时,排序合并可能比哈希连接更具成本效益。
    内存不足的哈希联接需要将哈希表和其他数据集都复制到磁盘。 在这种情况下,数据库可能必须多次从磁盘读取。 在排序合并中,如果内存不能保存两个数据集,则数据库将它们都写入磁盘,但读取每个数据集不超过一次。

9.2.3.2 How Sort Merge Joins Work

与嵌套循环连接一样,排序合并连接读取两个数据集,但在它们尚未排序时对它们进行排序。

对于第一个数据集中的每一行,数据库在第二个数据集中找到一个起始行,然后读取第二个数据集,直到找到一个不匹配的行。 在伪代码中,排序合并的高级算法可能如下所示:

READ data_set_1 SORT BY JOIN KEY TO temp_ds1
READ data_set_2 SORT BY JOIN KEY TO temp_ds2
 
READ ds1_row FROM temp_ds1
READ ds2_row FROM temp_ds2

WHILE NOT eof ON temp_ds1,temp_ds2
LOOP
    IF ( temp_ds1.key = temp_ds2.key ) OUTPUT JOIN ds1_row,ds2_row
    ELSIF ( temp_ds1.key <= temp_ds2.key ) READ ds1_row FROM temp_ds1
    ELSIF ( temp_ds1.key => temp_ds2.key ) READ ds2_row FROM temp_ds2
END LOOP

其实以上算法的前提,还是等值查询。

还是看个例子吧,本例中,外围表带索引,无需再排序:

set pages 9999
SELECT e.employee_id, e.last_name, e.first_name, e.department_id, 
       d.department_name
FROM   employees e, departments d
WHERE  e.department_id = d.department_id
ORDER BY department_id;


select * from table(dbms_xplan.display_cursor);

--------------------------------------------------------------------------------------------
| Id  | Operation                    | Name        | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |             |       |       |     6 (100)|          |
|   1 |  MERGE JOIN                  |             |   106 |  4028 |     6  (17)| 00:00:01 |
|   2 |   TABLE ACCESS BY INDEX ROWID| DEPARTMENTS |    27 |   432 |     2   (0)| 00:00:01 |
|   3 |    INDEX FULL SCAN           | DEPT_ID_PK  |    27 |       |     1   (0)| 00:00:01 |
|*  4 |   SORT JOIN                  |             |   107 |  2354 |     4  (25)| 00:00:01 |
|   5 |    TABLE ACCESS FULL         | EMPLOYEES   |   107 |  2354 |     3   (0)| 00:00:01 |
--------------------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   4 - access("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")
       filter("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")

这两个数据集是部门表和员工表。 因为索引按部门 ID 对部门表进行排序,所以数据库可以读取该索引并避免排序(步骤 3)。 数据库只需要对employees表进行排序(第4步),这是CPU最密集的操作。

假设外围表无索引(通过hint禁用),同样的SQL语句,其执行计划如下:

SELECT /*+ USE_MERGE(d e) NO_INDEX(d) */ e.employee_id, e.last_name, e.first_name, 
       e.department_id, d.department_name
FROM   employees e, departments d
WHERE  e.department_id = d.department_id
ORDER BY department_id;

-----------------------------------------------------------------------------------
| Id  | Operation           | Name        | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------
|   0 | SELECT STATEMENT    |             |       |       |     8 (100)|          |
|   1 |  MERGE JOIN         |             |   106 |  4028 |     8  (25)| 00:00:01 |
|   2 |   SORT JOIN         |             |    27 |   432 |     4  (25)| 00:00:01 |
|   3 |    TABLE ACCESS FULL| DEPARTMENTS |    27 |   432 |     3   (0)| 00:00:01 |
|*  4 |   SORT JOIN         |             |   107 |  2354 |     4  (25)| 00:00:01 |
|   5 |    TABLE ACCESS FULL| EMPLOYEES   |   107 |  2354 |     3   (0)| 00:00:01 |
-----------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   4 - access("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")
       filter("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")
 
Hint Report (identified by operation id / Query Block Name / Object Alias):
Total hints for statement: 1 (U - Unused (1))
---------------------------------------------------------------------------
 
   3 -  SEL$1 / "D"@"SEL$1"
         U -  USE_MERGE(d e)

因为部门.department_id 索引被忽略,优化器执行排序,这将步骤 2 和步骤 3 的综合成本增加了 33%(从 6 到 8)。

9.2.3.3 Sort Merge Join Controls

USE_MERGE 提示指示优化器使用排序合并连接。

在某些情况下,使用 USE_MERGE 提示覆盖优化器可能是有意义的。 例如,优化器可以选择对表进行全扫描并避免查询中的排序操作。 但是,成本会增加,因为通过索引和单块读取访问大表,而不是通过全表扫描更快地访问。

9.3 Join Types

连接类型由连接条件的类型决定。

9.3.1 Inner Joins

内连接(有时称为简单连接)是只返回满足连接条件的行的连接。 内连接是等值连接或非等值连接。

9.3.1.1 Equijoins

等值连接是一种内连接,其连接条件包含一个相等运算符。

以下示例是一个等值连接,因为连接条件仅包含一个相等运算符:

SELECT e.employee_id, e.last_name, d.department_name
FROM   employees e, departments d
WHERE  e.department_id=d.department_idSQL调优指南笔记3:SQL Processing

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

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

SQL调优指南笔记11:Histograms

SQL调优指南笔记11:Histograms

SQL调优指南笔记10:Optimizer Statistics Concepts