集群中 table 的执行计划需要一行,而它应该需要多行

Execution plan of table in cluster expects one row when it should expect multiple

我创建了一个集群,并在集群中创建了一个 table,定义如下:

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)
;

值得注意的是,这个索引不是用唯一约束创建的,很有可能将重复值插入 table 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);

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

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 / ROALD_DAHL_NOVELS@SEL
   2 - SEL / ROALD_DAHL_NOVELS@SEL

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' 预计只在集群中出现一次。执行计划还表示,对于该操作,它预计 return 只有一行。

列投影信息告诉我它期望 return 单个列(这将是 table ROALD_DAHL_NOVELS 的 ROWID),所以这告诉我总从该操作编辑的 ROWID 数 return 将为 1(1 行,每行 1 个 ROWID)。由于 table ROALD_DAHL_NOVELS 中的每一行都有不同的 ROWID,那么这个操作只能用于 return 来自 table.[=16 的单个行=]

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

此外,在TABLE ACCESS CLUSTER 中,table ROALD_DAHL_NOVELS 的ROWID 列在列投影信息部分。我没有尝试 select ROWID,所以我不希望它从该操作中被 returned。如果有的话,我希望它在谓词信息部分。

补充调查

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

UNIQUENESS  DISTINCT_KEYS   AVG_DATA_BLOCKS_PER_KEY
UNIQUE      1               109

据我了解,这告诉我们:

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

检查USER_EXTENTS时,索引只使用了一个65536字节的extent,这不足以space保存table中每个ROWID的信息。

实际执行计划(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 / ROALD_DAHL_NOVELS@SEL
   2 - SEL / ROALD_DAHL_NOVELS@SEL

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.

这不是错误。

运行 您数据库中的这个查询:

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

原因是B树集群索引只存储存储该数据的集群块的数据库块地址 -- 它不存储完整的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 报告的原因。因为就索引而言,这就是它正在做的事情。