【问题标题】:Execution plan of table in cluster expects one row when it should expect multiple集群中表的执行计划在应该期望多行时期望一行
【发布时间】:2020-09-28 21:15:00
【问题描述】:

我创建了一个集群和集群中的一个表,定义如下:

create cluster roald_dahl_titles (
   title varchar2(100)
);

create index i_roald_dahl_titles
on cluster roald_dahl_titles
;

create table ROALD_DAHL_NOVELS (
   title varchar2(100),
   published_year number
)
cluster roald_dahl_titles (title)
;

值得注意的是,这个索引不是使用唯一约束创建的,很可能会在 ROALD_DAHL_NOVELS 表中插入重复值:

insert into roald_dahl_novels (title, published_year) values ('Esio Trot', 1990);
insert into roald_dahl_novels (title, published_year) values ('Esio Trot', 1990);

然后我收集表和索引的统计信息,并查看使用索引的执行计划:

begin
  dbms_stats.gather_table_stats(user, 'ROALD_DAHL_NOVELS');
  dbms_stats.gather_INDEX_stats(user, 'I_ROALD_DAHL_TITLES');
end;
/

explain plan for
select published_year
  from roald_dahl_novels
 where title = 'Esio Trot';

select *
  from table(dbms_xplan.display(format => 'ALL'));

执行计划的内容我觉得有点混乱:

Plan hash value: 2187850431

--------------------------------------------------------------------------------------------
| Id  | Operation            | Name                | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |                     |     2 |    28 |     1   (0)| 00:00:01 |
|   1 |  TABLE ACCESS CLUSTER| ROALD_DAHL_NOVELS   |     2 |    28 |     1   (0)| 00:00:01 |
|*  2 |   INDEX UNIQUE SCAN  | I_ROALD_DAHL_TITLES |     1 |       |     0   (0)| 00:00:01 |
--------------------------------------------------------------------------------------------

Query Block Name / Object Alias (identified by operation id):
-------------------------------------------------------------

   1 - SEL$1 / ROALD_DAHL_NOVELS@SEL$1
   2 - SEL$1 / ROALD_DAHL_NOVELS@SEL$1

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

   2 - access("TITLE"='Esio Trot')

Column Projection Information (identified by operation id):
-----------------------------------------------------------

   1 - "ROALD_DAHL_NOVELS".ROWID[ROWID,10], "TITLE"[VARCHAR2,100], 
       "PUBLISHED_YEAR"[NUMBER,22]
   2 - "ROALD_DAHL_NOVELS".ROWID[ROWID,10]

作为操作 2 的一部分,它执行索引唯一扫描,这告诉我“Esio Trot”预计只会在集群中出现一次。执行计划还规定,对于该操作,它预计只返回一行。

列投影信息告诉我它期望返回单个列(这将是表 ROALD_DAHL_NOVELS 的 ROWID),因此这告诉我从该操作返回的 ROWID 总数将为 1(1 行位于每行 1 个 ROWID)。由于表 ROALD_DAHL_NOVELS 中的两行中的每一行都有不同的 ROWID,因此该操作只能用于从表中返回单行。

当执行 TABLE ACCESS CLUSTER 操作时,执行计划(正确地)期望返回两行,这让我感到困惑。如果 ROWID 正在访问这些行,那么我希望前面的操作返回(至少)两个 ROWID。如果它们没有被 ROWID 访问,我不希望前面的操作返回 ROWID。

此外,在 TABLE ACCESS CLUSTER 中,表 ROALD_DAHL_NOVELS 的 ROWID 列在列投影信息部分中。我没有尝试选择 ROWID,所以我不希望它从该操作中返回。如果在任何地方,我希望它出现在谓词信息部分。

额外调查

我尝试将同一行重复插入到表中,直到它包含同一行的 65536 个相同副本。在收集统计信息并查询索引 I_ROALD_DAHL_TITLES 的 USER_INDEXES 后,我们得到以下信息:

UNIQUENESS  DISTINCT_KEYS   AVG_DATA_BLOCKS_PER_KEY
UNIQUE      1               109

据我了解,这告诉我们:

  • 索引是唯一的,因此我们希望每个键在索引中出现一次
  • 索引只有一个不同的键('Esio Trot'),因此必须只有一个条目
  • 索引要求我们的一个键匹配表中的多行,跨越 109 个块

这似乎是自相矛盾的——一个键与表中的多行匹配意味着该键的索引中必须有多个条目(每个条目都与不同的 ROWID 匹配),这与索引的唯一性相矛盾。

在检查 USER_EXTENTS 时,索引仅使用 65536 字节的单个扩展区,这不足以容纳表中每个 ROWID 的信息。

【问题讨论】:

  • @MatthewMcPeak 即使它添加了 ROWID 以使其唯一,它仍然不能为表中的每一行都有一个条目,否则它会占用比 USER_EXTENTS 描述的更多空间
  • 我认为我的评论会令人困惑/不完整,所以我删除了它并写了一个答案。
  • 我不得不说看到一个关于集群的问题给我留下了深刻的印象。我已经使用 Oracle 30 年了,这是第一次有人使用它。
  • @WilliamRobertson 这更像是一个概念证明而不是一个主要项目 - 我有多个表表示存在于同一级别的数据并且有很多重叠的列,但是在不同的时间计算并且会不适合存放在一张宽大的桌子上。

标签: sql oracle sql-execution-plan


【解决方案1】:

这不是错误。

在您的数据库中运行此查询:

select UNIQUENESS from dba_indexes where index_name = upper('i_roald_dahl_titles');
UNIQUENES
---------
UNIQUE

原因是 B-tree 集群索引只存储存储该数据的集群块的数据库块地址——它不像普通的那样存储完整的 rowid 值索引会。

因此,虽然 title = 'Esio Trot' 的各个行可能具有 rowid 值,例如:

select rowid row_id, title from roald_dahl_novels n;
ROW_ID             TITLE                                                                                               
------------------ ----------------------------------------------------------------------------------------------------
ABocNnACmAABWsWAAL Esio Trot                                                                                           
ABocNnACmAABWsWAAM Esio Trot                                                                                           
ABocNnACmAABWsWAAN Esio Trot

B-tree 集群索引只存储一个条目:“Esio Trot”,以及对应的数据库块地址。您可以在您的数据库中确认这一点:

select num_rows from dba_indexes where index_Name = 'I_ROALD_DAHL_TITLES';
   NUM_ROWS
 ----------
          1

这就是您收到UNIQUE SCAN 报告的原因。因为就索引而言,这就是它正在做的事情。

【讨论】:

  • 所以索引唯一扫描返回单行,列投影信息告诉我们这是表 ROALD_DAHL_NOVELS 的单个 ROWID。这如何让它在下一个操作中从表中获取两行?
  • 集群索引提供的数据库块地址足以让 Oracle 在集群中找到正确的块(在TABLE ACCESS CLUSTER 计划步骤中)。由于您没有进一步的过滤条件,因此结果包括这些块中的任何记录。不满足索引条件的行不会存储在这些数据库块中,这是集群的本质。
  • 澄清一下,INDEX UNIQUE SCAN 并没有返回“单行”。它为集群中的块返回单个数据库块地址。该块中可能有几行,但您的集群索引不知道/不在乎。
  • 这就是幕后发生的过程吗? - 使用索引 I_ROALD_DAHL_TITLES 查找 ROALD_DAHL_TITLES 中单个块的地址 - 通过该地址访问 ROALD_DAHL_TITLES 中的该块 - 该块包含 ROALD_DAHL_NOVELS 中每一行的 ROWID - ROALD_DAHL_NOVELS 中的每一行都由 ROWID 访问跨度>
  • 对于一个cluster,基本上就是这个过程——除了最后一句:“ROALD_DAHL_NOVELS 中的每一行都由ROWID 访问”。集群中的块通过其数据库块地址访问。然后按顺序从块中读出各个行。块中每一行的ROWID是事先不知道的,也没有必要。
【解决方案2】:

实际执行计划(在 19.5 中测试)存在相同的问题。 可能是集群对象显示的执行计划的限制或错误。我会在 asktom.oracle.com 上问这个问题,以获得 Oracle 的某种官方(免费)答案。

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID  f41cf1x2zdyyr, child number 0
-------------------------------------
select published_year   from roald_dahl_novels  where title = 'Esio
Trot'

Plan hash value: 2187850431

--------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation        | Name        | Starts | E-Rows |E-Bytes| Cost (%CPU)| E-Time   | A-Rows |   A-Time   | Buffers |
--------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT               |               |      1 |        |       |     1 (100)|      |      2 |00:00:00.01 |       3 |
|   1 |  TABLE ACCESS CLUSTER| ROALD_DAHL_NOVELS   |      1 |      2 |    28 |     1   (0)| 00:00:01 |      2 |00:00:00.01 |       3 |
|*  2 |   INDEX UNIQUE SCAN  | I_ROALD_DAHL_TITLES |      1 |      1 |       |     0   (0)|      |      1 |00:00:00.01 |       1 |
--------------------------------------------------------------------------------------------------------------------------------------

Query Block Name / Object Alias (identified by operation id):
-------------------------------------------------------------

   1 - SEL$1 / ROALD_DAHL_NOVELS@SEL$1
   2 - SEL$1 / ROALD_DAHL_NOVELS@SEL$1

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

   2 - access("TITLE"='Esio Trot')

Column Projection Information (identified by operation id):

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------------------------------------------------------------------
-----------------------------------------------------------

   1 - "ROALD_DAHL_NOVELS".ROWID[ROWID,10], "TITLE"[VARCHAR2,100], "PUBLISHED_YEAR"[NUMBER,22]
   2 - "ROALD_DAHL_NOVELS".ROWID[ROWID,10]


32 rows selected.

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-11-07
    • 1970-01-01
    • 1970-01-01
    • 2020-04-08
    • 2023-03-30
    • 2018-10-05
    • 1970-01-01
    相关资源
    最近更新 更多