1

我的理解是,嵌套循环连接的内部行源的解释计划中的估计行数反映了该嵌套循环仅一次迭代的行数。

在以下示例中,解释计划的第 6 步是嵌套循环连接的内部行源,该连接一次通过一个 ROWID 获取行。因此,估计应该有 1 行(每个 ROWID 只有 1 行)。

为什么第 6 步table access by index ROWID显示 100(我希望它显示 1)?

使用 Oracle 19c 企业版。

drop table "C";
drop table "P";

create table "P"
  ( p_id  NUMBER
  , p_num NUMBER
  , p_pad CHAR(200 byte)
  )
;

insert
  into "P" (p_id, p_num, p_pad)
  select level
       , mod(level-1,200/2)
       , ' '
    from dual
    connect by level <= 200
;

create table "C"
  ( c_id  NUMBER
  , p_id  NUMBER
  , c_pad CHAR(200 byte)
  )
;

insert /*+ append enable_parallel_dml parallel (auto) */
  into "C" (c_id, p_id, c_pad)
  with
    "D" as
    ( select /*+ materialize */ null from dual connect by level <= 100
    )
    select rownum      c_id
         , p_id        p_id
         , ' '         c_pad
      from "P", "D"
;
commit;
create index IX_P on p (p_num);   
create unique index IU_P on p (p_id);
alter table p add constraint UK_P unique (p_id) rely using index IU_P enable validate;
alter table C add constraint R_C foreign key (p_id) references p (p_id) rely enable validate;
create index IR_C on _C (p_id);
exec dbms_stats.gather_table_stats(OwnName => null, TabName => 'P', cascade => true);
exec dbms_stats.gather_table_stats(OwnName => null, TabName => 'C', cascade => true);
select /*+ optimizer_features_enable('19.1.0')
           use_nl (P C) */
       *
  from "P"
       join "C"
         on P.p_id = C.p_id
            and P.p_num = 1
;
plan hash value: 3840235794

----------------------------------------------------------------------------------------------
| id  | Operation                             | name | rows  | Bytes | cost (%CPU)| time     |
----------------------------------------------------------------------------------------------
|   0 | select statement                      |      |   200 | 83000 |   205   (0)| 00:00:01 |
|   1 |  nested LOOPS                         |      |   200 | 83000 |   205   (0)| 00:00:01 |
|   2 |   nested LOOPS                        |      |   200 | 83000 |   205   (0)| 00:00:01 |
|   3 |    table access by index ROWID BATCHED| P    |     2 |   414 |     3   (0)| 00:00:01 |
|*  4 |     index range scan                  | IX_P |     2 |       |     1   (0)| 00:00:01 |
|*  5 |    index range scan                   | IR_C |   100 |       |     1   (0)| 00:00:01 |
|   6 |   table access by index ROWID         | C    |   100 | 20800 |   101   (0)| 00:00:01 |
----------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------

   4 - access("P"."P_NUM"=1)
   5 - access("P"."P_ID"="C"."P_ID")

外部行源步骤 3 乘以内部行源步骤 5 = 嵌套循环步骤 2。

但是,外部行源步骤 2 乘以内部行源步骤 6 <> 嵌套循环步骤 1。

我同意第 1 步的总数应该是 200,但不明白为什么第 6 步估计有 100 行。

为什么步骤 6 的估计行数为 100 而不是 1?

提前致谢。

4

2 回答 2

2

在这里,您可以看到外部预期的行数NESTED LOOP

select p_id, count(*) from C where p_id in (
select p_id from P where p_num = 1)
group by p_id;

      P_ID   COUNT(*)
---------- ----------
         2        100
       102        100

所以实际上每次迭代都希望得到 100 行。

如果您使用提示运行查询,gather_plan_statistics您可以看到实际行数Starts和总行数A-Rows

select /*+ gather_plan_statistics use_nl (P C) */
       *
  from "P"
       join "C"
         on P.p_id = C.p_id
            and P.p_num = 1

SQL_ID  927pggk6scpwt, child number 0
-------------------------------------
select /*+ gather_plan_statistics use_nl (P C) */        *   from "P"   
     join "C"          on P.p_id = C.p_id             and P.p_num = 1
 
Plan hash value: 2326820011
 
--------------------------------------------------------------------------------------------------------
| Id  | Operation                             | Name | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
--------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                      |      |      1 |        |    200 |00:00:00.01 |     213 |
|   1 |  NESTED LOOPS                         |      |      1 |    200 |    200 |00:00:00.01 |     213 |
|   2 |   NESTED LOOPS                        |      |      1 |    200 |    200 |00:00:00.01 |      13 |
|   3 |    TABLE ACCESS BY INDEX ROWID BATCHED| P    |      1 |      2 |      2 |00:00:00.01 |       5 |
|*  4 |     INDEX RANGE SCAN                  | IX_P |      1 |      2 |      2 |00:00:00.01 |       3 |
|*  5 |    INDEX RANGE SCAN                   | IR_C |      2 |    100 |    200 |00:00:00.01 |       8 |
|   6 |   TABLE ACCESS BY INDEX ROWID         | C    |    200 |    100 |    200 |00:00:00.01 |     200 |
--------------------------------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   4 - access("P"."P_NUM"=1)
   5 - access("P"."P_ID"="C"."P_ID")

即操作 5启动了两次(列Starts),总共200 行(列A-Rows

操作 6 每次启动 200 次以获得排。

于 2021-11-10T15:21:44.707 回答
0

我认为这个 Oracle 文档段落很好地解释了这种情况:

多个嵌套循环操作偶尔会出现在一个连接的执行计划中,这表明 Oracle 使用了嵌套循环批处理优化技术。该方法所做的是将两个行源的单个连接转换为驱动行源的连接到探测行源的一个副本,该副本连接到 ROWID 上自身的副本;因为我们现在有三个行源,我们至少需要两个嵌套循环。用于对 ROWID 执行自连接的探测行源副本用于过滤行,因此它TABLE ACCESS BY ... ROWID在执行计划中会有相应的条目。这种基于成本的优化通常可以减少 I/O,尽管执行计划可能不会透明地显示收益。

您示例中的第 6 步是“探测行源副本”;它基本上是表 C 的缓存版本,所以它有 100 行。但是它的成本在所有外部嵌套循环之间共享——该表只被访问过一次——所以它已经包含在第 2 步的总数中。(我认为?)

于 2021-11-10T15:15:34.060 回答