Oracle联接消除在联接两个以上的表时未按预期工作
联接两个表时,联接消除工作正常:Oracle联接消除在联接两个以上的表时未按预期工作,oracle,sqlperformance,Oracle,Sqlperformance,联接两个表时,联接消除工作正常: SQL> set lines 200; SQL> SQL> select * from v$version; BANNER CON_ID
SQL> set lines 200;
SQL>
SQL> select * from v$version;
BANNER CON_ID
-------------------------------------------------------------------------------- ----------
Oracle Database 12c Release 12.1.0.1.0 - 64bit Production 0
PL/SQL Release 12.1.0.1.0 - Production 0
CORE 12.1.0.1.0 Production 0
TNS for Linux: Version 12.1.0.1.0 - Production 0
NLSRTL Version 12.1.0.1.0 - Production 0
SQL>
SQL> create table t01 (
2 id integer,
3 apk varchar2(255 char),
4 constraint pk_01 primary key (id)
5 );
Tabelle wurde erstellt.
SQL> create table t02 (
2 id integer,
3 apk varchar2(255 char),
4 id_t01 integer,
5 constraint pk_02 primary key (id),
6 constraint fk_02 foreign key (id_t01) references t01(id)
7 );
Tabelle wurde erstellt.
SQL> create table t03 (
2 id integer,
3 apk varchar2(255 char),
4 id_t02 integer,
5 constraint pk_03 primary key (id),
6 constraint fk_03 foreign key (id_t02) references t02(id)
7 );
Tabelle wurde erstellt.
SQL> create index ix_t03 on t03(id_t02);
Index wurde erstellt.
SQL> create index ix_t02 on t02(id_t01);
Index wurde erstellt.
SQL> insert into t01 (id, apk)
2 select level, to_char(level)
3 from dual
4 connect by level <= 1000;
1000 Zeilen erstellt.
SQL> insert into t02(id, apk, id_t01)
2 select id, apk, id from t01;
1000 Zeilen erstellt.
SQL> insert into t03(id, apk, id_t02)
2 select id, apk, id from t01;
1000 Zeilen erstellt.
SQL> commit;
Transaktion mit COMMIT abgeschlossen.
SQL>
SQL> exec dbms_stats.gather_table_stats(null, 'T01', method_opt=>'for all columns size skewonly', cascade=>true);
PL/SQL-Prozedur erfolgreich abgeschlossen.
SQL> exec dbms_stats.gather_table_stats(null, 'T02', method_opt=>'for all columns size skewonly', cascade=>true);
PL/SQL-Prozedur erfolgreich abgeschlossen.
SQL> exec dbms_stats.gather_table_stats(null, 'T03', method_opt=>'for all columns size skewonly', cascade=>true);
PL/SQL-Prozedur erfolgreich abgeschlossen.
SQL> commit;
Transaktion mit COMMIT abgeschlossen.
SQL>
SQL> set autotrace traceonly explain;
SQL>
SQL> select t02.id
2 from t02
3 left join t01 on t01.id = t02.id_t01;
Ausführungsplan
----------------------------------------------------------
--------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost |
--------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1000 | 8000 | 2 |
| 1 | INDEX FAST FULL SCAN| PK_02 | 1000 | 8000 | 2 |
--------------------------------------------------------------
-->对t03的主键索引进行快速完全扫描,不读取t02。这就是我所期待的
当我试图加入t01、t02和t03时,问题出现了:
SQL> select t03.id
2 from t03
3 left join t02 on t02.id = t03.id_t02
4 left join t01 on t01.id = t02.id_t01;
Ausführungsplan
----------------------------------------------------------
------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost |
------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1000 | 16000 | 18 |
| 1 | NESTED LOOPS OUTER| | 1000 | 16000 | 18 |
| 2 | TABLE ACCESS FULL| T03 | 1000 | 8000 | 18 |
| 3 | INDEX UNIQUE SCAN| PK_02 | 1 | 8 | 0 |
------------------------------------------------------------
在这里,我希望(仅)对pk_03进行完整的索引扫描,但执行计划在T03和PK02之间执行嵌套循环
我做错了什么?我有错误的期望吗?我在Oracle文档/stackoverflow/google中找不到解释这种行为的任何内容
我正在使用的实际数据库确实有更多的列/表等,这只是一个最小的示例。当连接20个表时,问题会变得更糟,并且预期的连接消除不会发生。这对查询执行时间有很大的负面影响
多谢各位
select t03.id
from t03
left join t02 on t02.id = t03.id_t02
left join t01 on t01.id = t02.id_t01;
可以从此查询中删除表t01,因为不需要读取该表中的任何内容来确定t02中哪些行具有指向t01的有效FK-您可以只检查PK索引而不是表
要检查t03中的哪些行具有指向t02的有效FK,也不需要读取t02
但要将这两组行连接在一起,需要读取t02的所有内容,以查看一组中的哪些行具有与另一组中的行匹配的ID。索引本身没有足够的信息来进行此联接
,“索引记录只包含索引字段和指向原始记录的指针”-这意味着fk_03(在t03上)看起来有点像:
id_t02 t02_rowid
873 AAEiyFAAVAAA49bAAA
874 AAEiyFAAVAAA49bAAB
...
要将它们连接在一起,您需要询问:t02行id中的哪一个(来自第一个索引)指向第二个索引中id为_t01的记录?而索引本身并没有这些信息——您必须读取表
实际上,在回顾时,我认为问题更多的是将t03.id与pk_02结果进行匹配。对不起,我不是这方面的专家
无论如何,您可以将该信息添加到复合索引中:
create index ix_t0302 on t03(id_t02, id);
这改变了解释计划:
-------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 10 | 160 | 2 (0)| 00:00:01 |
| 1 | NESTED LOOPS OUTER| | 10 | 160 | 2 (0)| 00:00:01 |
| 2 | INDEX FULL SCAN | IX_T0302 | 10 | 80 | 1 (0)| 00:00:01 |
|* 3 | INDEX UNIQUE SCAN| PK_02 | 1 | 8 | 1 (0)| 00:00:01 |
-------------------------------------------------------------------------------
*编辑以纠正我自己优化器必须认为查询计划比备选方案执行得更有效。你确定这是个问题吗?如果是,您是否尝试过使用查询提示,例如没有嵌套循环等?pk_03上的快速完全扫描成本为2(请参阅t02和t03之间的连接),t01、t02和t03之间的连接的执行计划成本为18。这正是我不明白的,为什么不选择成本较低=2的计划?我的生产数据库中的实际查询被表示为一个视图,该视图被许多其他查询使用;虽然我可以尝试调整这个特定的示例,但我不可能对所有可能的查询组合使用查询提示。您的代码在版本12.1.0.2中工作正常。我建议您检查support.oracle.com。@Matthew McPeak看起来这个问题实际上与oracle版本有关:它在oracle 12.2.0.1.0和11.2.0.4.0中正常工作-尚未检查12.1.0.2。非常感谢。
-------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 10 | 160 | 2 (0)| 00:00:01 |
| 1 | NESTED LOOPS OUTER| | 10 | 160 | 2 (0)| 00:00:01 |
| 2 | INDEX FULL SCAN | IX_T0302 | 10 | 80 | 1 (0)| 00:00:01 |
|* 3 | INDEX UNIQUE SCAN| PK_02 | 1 | 8 | 1 (0)| 00:00:01 |
-------------------------------------------------------------------------------