用条款解释Oracle计划显示合并合并笛卡尔

时间:2018-11-16 06:01:54

标签: oracle oracle12c query-performance sql-execution-plan

我试图通过将条件LEFT JOIN重写为UNION的{​​{1}}和基表来改善下面所示查询的性能。我正在使用Oracle 12c。

有关的表是INNER JOINASSIGNMENTS。要求是对于给定的CLASSES值,我们需要从两个表中的相应记录中获取详细信息。

  • 每个ITEM_ID仅存在一个ITEM_ID表记录。
  • CLASSES表可能有也可能没有CLASSES表中任何给定ITEM_ID的记录。
  • 仅当CLASSES记录具有ASSIGNMENTS ='Y'时才需要ASSIGNMENTS记录详细信息。

我提出的查询是:

enable_capacity

从本质上讲,WITH子句语句将是互斥的-也就是说,只有一个WITH子句查询将具有给定WITH CLASSES_WITH_CAPACITY AS ( SELECT maximum_attendees, item_id, enable_capacity FROM CLASSES classes WHERE enable_capacity = 'Y' AND classes.item_id = 123 ), CLASSES_WITHOUT_CAPACITY AS ( SELECT maximum_attendees, item_id, enable_capacity FROM CLASSES classes WHERE enable_capacity is null AND item_id = 123 ) SELECT maximum_attendees, item_id, max_position_value, enable_capacity, enable_waitlist FROM ( (SELECT classes.maximum_attendees, classes.item_id, MAX(assignments.wait_position) max_position_value, classes.enable_capacity, classes.enable_waitlist FROM CLASSES_WITH_CAPACITY classes JOIN WLF_ASSIGNMENT_RECORDS_F assignments ON (classes.item_id = assignments.item_id) WHERE ( assignments.status <> 'EXPIRED') GROUP BY classes.item_id, classes.maximum_attendees, classes.enable_capacity, classes.enable_waitlist ) UNION ALL (SELECT classes.maximum_attendees, classes.item_id, null AS max_position_value, classes.enable_capacity, classes.enable_waitlist FROM CLASSES_WITHOUT_CAPACITY classes ) ); 的单个CLASSES记录。以下是上述查询的EXPLAIN PLAN的输出:

ITEM_ID

现在,我担心的是联接类型为------------------------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 2 | 244 | 8 (13)| 00:00:01 | | 1 | VIEW | | 2 | 244 | 8 (13)| 00:00:01 | | 2 | UNION-ALL | | | | | | | 3 | HASH GROUP BY | | 1 | 111 | 6 (17)| 00:00:01 | | 4 | MERGE JOIN CARTESIAN | | 1 | 111 | 5 (0)| 00:00:01 | |* 5 | TABLE ACCESS BY INDEX ROWID | ASSIGNMENTS | 1 | 75 | 4 (0)| 00:00:01 | |* 6 | INDEX RANGE SCAN | ASSIGNMENTS_N9 | 9 | | 1 (0)| 00:00:01 | | 7 | BUFFER SORT | | 1 | 36 | 2 (50)| 00:00:01 | |* 8 | TABLE ACCESS BY INDEX ROWID BATCHED| CLASSES | 1 | 36 | 1 (0)| 00:00:01 | |* 9 | INDEX RANGE SCAN | CLASSES_N2 | 1 | | 0 (0)| 00:00:01 | |* 10 | TABLE ACCESS BY INDEX ROWID BATCHED | CLASSES | 1 | 36 | 2 (0)| 00:00:01 | |* 11 | INDEX RANGE SCAN | CLASSES_N2 | 1 | | 1 (0)| 00:00:01 | ------------------------------------------------------------------------------------------------------------- 。似乎在MERGE JOIN CARTESIANINNER JOIN之间的CLASSES使用MERGE JOIN CARTESIAN。

我的最初印象是,这不会导致性能下降,仅是因为仅对一条记录执行联接。这是正确的吗?

完全使用笛卡尔联接类型的原因是什么?

鉴于在缺少JOIN谓词和/或联接的谓词不符合正确的主-外键关系时使用笛卡尔联接类型的事实,是优化程序未在CLASSES别名上找到任何索引?也就是说,当进一步加入WITH子句时,使用WITH子句会掩盖WITH子句中表中存在的索引吗?

如果是这样,是否应该不鼓励使用WITH子句-尤其是在进一步连接WITH子句的输出时?

此外,也欢迎对我的方法提出任何其他建议。 谢谢!

1 个答案:

答案 0 :(得分:0)

为什么使用CARTESIAN JOIN?

正如您所说,表CLASSES中最多有一条记录,其中有给定的item_id。 Oracle实现了这一点(请参见执行计划第9行的第Rows行),并决定对此行进行笛卡尔联接。 表ASSIGNMENTS的预期9行。

如果基数估计正确,这可以正常工作,但是如果不正确,则可能会造成大麻烦。我想item_id没有主键或唯一索引的支持-请参见第9行的INDEX RANGE SCAN;我希望在唯一索引的情况下使用INDEX UNIQUE SCAN。因此,这是造成问题的潜在原因。

WITH子句的用法

正如其他人所提到的,查询中的WITH子句无效,但也没有害处。如您在执行计划中所见,它们是 在主查询中合并。 Oracle正在重新编写查询,并消除了两个子查询。

提高性能

您没有说明通过消除LEFT OUTER JOIN来解决的问题;无论如何,可能会有一个微妙的地方,需要一些照顾 被采取。

基本上有两种执行计划的可能性

1)首先加入,然后进行聚集以获得MAX(wait_position)

2)首先获取MAX值,然后将两个单行表联接(平凡)

如果表ASSINMENT仅具有给定ITEM_ID的行数,则这两个选项之间几乎没有区别。

如果遇到ITEM_ID行的行分配问题,麻烦就开始了。这有时被称为“嵌套循环中的垂死事件”, 仅在将结果汇总到包含MAX的一行之后,您才循环数百万行。

在这种情况下,Oracle具有索引访问权限INDEX RANGE SCAN (MIN/MAX),该访问权限直接从索引获取最大值(无需访问表且无需扫描所有值-请记住对索引进行排序,因此获取MAX值是微不足道的)。

因此,在这些索引假定的情况下,您可以尝试以下替代查询

类的唯一索引(item_id) WLF_ASSIGNMENT_RECORDS_F上的索引(item_id,wait_position)

第二个索引很重要,因为您将完全绕过该表并将其移到max(wait_position)

查询(通过省略一些列来简化)如下:

with max_wait as (
select  max(a.wait_position) max_wait_position
from assignments a
where a.item_id = 1)
select c.item_id, m.max_wait_position
from classes c
left outer join max_wait m
on  c.enable_capacity = 'Y'
where c.item_id = 1;

请注意,子查询将计算max(wait_position)。 在下一步中,您outer joins将表---------------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ---------------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 18 | 3 (0)| 00:00:01 | | 1 | NESTED LOOPS OUTER | | 1 | 18 | 3 (0)| 00:00:01 | | 2 | TABLE ACCESS BY INDEX ROWID | CLASSES | 1 | 5 | 1 (0)| 00:00:01 | |* 3 | INDEX UNIQUE SCAN | CLASSES_UX1 | 1 | | 0 (0)| 00:00:01 | | 4 | VIEW | VW_LAT_1D42B1AA | 1 | 13 | 2 (0)| 00:00:01 | |* 5 | FILTER | | | | | | | 6 | VIEW | | 1 | 13 | 2 (0)| 00:00:01 | | 7 | SORT AGGREGATE | | 1 | 9 | | | | 8 | FIRST ROW | | 1 | 9 | 2 (0)| 00:00:01 | |* 9 | INDEX RANGE SCAN (MIN/MAX)| ASSIGNMENTS_IX1 | 1 | 9 | 2 (0)| 00:00:01 | ---------------------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 3 - access("C"."ITEM_ID"=1) 5 - filter("C"."ENABLE_CAPACITY"='Y') 9 - access("A"."ITEM_ID"=1) 子查询,但仅当c.enable_capacity ='Y'时-不需要进一步的谓词,因为两个行源都具有最大一行。

执行计划非常有效,它由两个索引访问和一个表块访问组成。

item_id

我喜欢这个示例作为演示,一个简单而紧凑的书面查询可以得出有效的执行计划。

最后一句话,我希望在您的生产环境中,您使用绑定变量而不是package basic type Headers struct { } type Body struct { } 的文字键。)