使用左外连接时,Oracle Partition Pruning无法正常工作

时间:2014-09-26 19:47:14

标签: oracle join oracle11g partitioning database-partitioning

我有一个表在数字列(row_id)上分区列表,

TABLEA (ROW_ID NUMERIC(38), TB_KEY NUMERIC(38), ROW_DATA VARCHAR(20));
当我从没有连接的表中查询时,

分区修剪工作

SELECT A.* FROM TABLEA A
WHERE ROW_ID IN (SELECT ID FROM TABLEB WHERE DT_COL = SYSDATE);
当我将外连接保留到TableB

时,

分区修剪失败

SELECT A.* FROM TABLEA A
LEFT OUTER JOIN TABLEB B ON A.TB_KET = B.TB_KEY
WHERE ROW_ID IN (SELECT ID FROM TABLEB WHERE DT_COL = SYSDATE);
当我将左外连接更改为内连接

时,

分区修剪工作

SELECT A.* FROM TABLEA A
INNER JOIN TABLEB B ON A.TB_KET = B.TB_KEY
WHERE ROW_ID IN (SELECT ID FROM TABLEB WHERE DT_COL = SYSDATE);
当我将外连接保留到TableB并且不使用IN子句

时,

分区修剪工作

SELECT A.* FROM TABLEA A
LEFT OUTER JOIN TABLEB B ON A.TB_KET = B.TB_KEY
WHERE ROW_ID = 123;

分区修剪工作当我将外连接保留到TableB并为IN子句使用静态值

SELECT A.* FROM TABLEA A
LEFT OUTER JOIN TABLEB B ON A.TB_KET = B.TB_KEY
WHERE ROW_ID IN (123, 345);

有人可以解释一下为什么左外连接会导致分区修剪失败,当我在列上查询表是使用带有子查询结果的IN子句进行分区时?

2 个答案:

答案 0 :(得分:1)

Oracle 11g的答案是肯定的,分区修剪工作正常。

您的设置中有三种主要的访问模式,list partitionedTABLEA,让我们全部通过。请注意,我正在使用 最简单的陈述来说明行为。

使用相等谓词或IN列表中的键进行访问

最简单的情况是在分区键上使用等于谓词的文字:

SELECT A.* FROM TABLEA A
LEFT OUTER JOIN TABLEB B ON A.TB_KET = B.TB_KEY
WHERE A.ROW_ID = 123;

这导致遵循执行计划

-------------------------------------------------------------------------------------------------
| Id  | Operation              | Name   | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
-------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT       |        |     1 |    51 |     4   (0)| 00:00:01 |       |       |
|*  1 |  HASH JOIN OUTER       |        |     1 |    51 |     4   (0)| 00:00:01 |       |       |
|   2 |   PARTITION LIST SINGLE|        |     1 |    38 |     2   (0)| 00:00:01 |   KEY |   KEY |
|*  3 |    TABLE ACCESS FULL   | TABLEA |     1 |    38 |     2   (0)| 00:00:01 |     2 |     2 |
|   4 |   TABLE ACCESS FULL    | TABLEB |     1 |    13 |     2   (0)| 00:00:01 |       |       |
-------------------------------------------------------------------------------------------------

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

   1 - access("A"."TB_KET"="B"."TB_KEY"(+))
   3 - filter("A"."ROW_ID"=123)

仅访问TABLEA的相关分区(此处为分区#2) - 请参阅PstartPstop列。

IN LIST

中的情况略有复杂但相似
SELECT A.* FROM TABLEA A
LEFT OUTER JOIN TABLEB B ON A.TB_KET = B.TB_KEY
WHERE ROW_ID IN (123, 345);

-------------------------------------------------------------------------------------------------
| Id  | Operation              | Name   | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
-------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT       |        |     1 |    51 |     4   (0)| 00:00:01 |       |       |
|*  1 |  HASH JOIN OUTER       |        |     1 |    51 |     4   (0)| 00:00:01 |       |       |
|   2 |   PARTITION LIST INLIST|        |     1 |    38 |     2   (0)| 00:00:01 |KEY(I) |KEY(I) |
|*  3 |    TABLE ACCESS FULL   | TABLEA |     1 |    38 |     2   (0)| 00:00:01 |KEY(I) |KEY(I) |
|   4 |   TABLE ACCESS FULL    | TABLEB |     1 |    13 |     2   (0)| 00:00:01 |       |       |
-------------------------------------------------------------------------------------------------

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

   1 - access("A"."TB_KET"="B"."TB_KEY"(+))
   3 - filter("A"."ROW_ID"=123 OR "A"."ROW_ID"=345)

在这种情况下,可以访问更多分区,但只考虑包含IN LIST中密钥的那些分区。

相同对于使用绑定变量的访问有效。

使用NESTED LOOPS

从表格中访问

两个表连接的情况更复杂。使用嵌套循环加入来自TABLEB的每个键 访问TABLEA。这意味着对于每个密钥,只访问密钥所在的一个分区。

SELECT  A.* FROM TABLEA A
WHERE ROW_ID IN (SELECT ID FROM TABLEB WHERE DT_COL = SYSDATE);

---------------------------------------------------------------------------------------------------
| Id  | Operation                | Name   | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
---------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT         |        |     1 |    60 |     4  (25)| 00:00:01 |       |       |
|   1 |  NESTED LOOPS            |        |     1 |    60 |     4  (25)| 00:00:01 |       |       |
|   2 |   SORT UNIQUE            |        |     1 |    22 |     2   (0)| 00:00:01 |       |       |
|*  3 |    TABLE ACCESS FULL     | TABLEB |     1 |    22 |     2   (0)| 00:00:01 |       |       |
|   4 |   PARTITION LIST ITERATOR|        |   100K|  3710K|     1   (0)| 00:00:01 |   KEY |   KEY |
|*  5 |    TABLE ACCESS FULL     | TABLEA |   100K|  3710K|     1   (0)| 00:00:01 |   KEY |   KEY |
---------------------------------------------------------------------------------------------------

同样有一个分区修剪KEY - KEY,所以只访问带有来自TABLEB的密钥的分区,但从嵌套循环的性质来看,可以多次访问一个分区(对于不同的键)。

使用HASH JOIN从表格访问

使用HASH JOIN是最复杂的情​​况,其中分区修剪必须在连接开始之前发生。这是工作中的Bloom Filter

它是如何工作的?在扫描TABLEB Oracle知道所有相关密钥后,这些密钥可以映射到相关分区和a 创建了这些分区的Bloom Filter (BF)(操作3和2)。 BF传递给TABLEA并用于分区修剪(操作4和5)。

-------------------------------------------------------------------------------------------------------
| Id  | Operation                   | Name    | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
-------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |         |   100K|  5859K|     5  (20)| 00:00:01 |       |       |
|*  1 |  HASH JOIN RIGHT SEMI       |         |   100K|  5859K|     5  (20)| 00:00:01 |       |       |
|   2 |   PART JOIN FILTER CREATE   | :BF0000 |     1 |    22 |     2   (0)| 00:00:01 |       |       |
|*  3 |    TABLE ACCESS FULL        | TABLEB  |     1 |    22 |     2   (0)| 00:00:01 |       |       |
|   4 |   PARTITION LIST JOIN-FILTER|         |   100K|  3710K|     2   (0)| 00:00:01 |:BF0000|:BF0000|
|   5 |    TABLE ACCESS FULL        | TABLEA  |   100K|  3710K|     2   (0)| 00:00:01 |:BF0000|:BF0000|
-------------------------------------------------------------------------------------------------------

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

   1 - access("ROW_ID"="ID")
   3 - filter("DT_COL"=SYSDATE@!)

请参阅PstartPstop :BFnnnn作为Bloom过滤器的标志。

答案 1 :(得分:0)

分区修剪可以使用LEFT OUTER JOININ子查询。您可能会看到一个非常具体的问题,需要更具体的测试用例。

示例架构

drop table tableb purge;
drop table tablea purge;

create table tablea (row_id numeric(38),tb_key numeric(38),row_data varchar(20))
 partition by list(row_id)
 (partition p1 values (1),partition p123 values(123),partition p345 values(345));
create table tableb (id numeric(38), dt_col date, tb_key numeric(38));

begin
    dbms_stats.gather_table_stats(user, 'TABLEA');
    dbms_stats.gather_table_stats(user, 'TABLEB');
end;
/

<强>查询

--Partition pruning works when i query from table with no joins:
explain plan for 
SELECT A.* FROM TABLEA A
WHERE ROW_ID IN (SELECT ID FROM TABLEB WHERE DT_COL = SYSDATE);
select * from table(dbms_xplan.display);

--Partition Pruning fails when I do left outer join to TableB
explain plan for
SELECT A.* FROM TABLEA A
LEFT OUTER JOIN TABLEB B ON A.TB_KEY = B.TB_KEY
WHERE ROW_ID IN (SELECT ID FROM TABLEB WHERE DT_COL = SYSDATE);
select * from table(dbms_xplan.display);

--Partition Pruning works when I change left outer join to inner join
explain plan for
SELECT A.* FROM TABLEA A
INNER JOIN TABLEB B ON A.TB_KEY = B.TB_KEY
WHERE ROW_ID IN (SELECT ID FROM TABLEB WHERE DT_COL = SYSDATE);
select * from table(dbms_xplan.display);

--Partition Pruning works when I do left outer join to TableB and do not use IN clause
explain plan for
SELECT A.* FROM TABLEA A
LEFT OUTER JOIN TABLEB B ON A.TB_KEY = B.TB_KEY
WHERE ROW_ID = 123;
select * from table(dbms_xplan.display);

--Partition Pruning works when I do left outer join to TableB and use static values for IN clause
explain plan for
SELECT A.* FROM TABLEA A
LEFT OUTER JOIN TABLEB B ON A.TB_KEY = B.TB_KEY
WHERE ROW_ID IN (123, 345);
select * from table(dbms_xplan.display);

<强>输出

此处不显示完整的执行计划,以节省空间。唯一重要的列是PstartPstop,这意味着使用分区修剪。

执行计划看起来像下列之一:

... -----------------
... | Pstart| Pstop |
... -----------------
... 
... |   KEY |   KEY |
... |   KEY |   KEY |
... 
... -----------------

OR

... -----------------
... | Pstart| Pstop |
... -----------------
... 
... |     2 |     2 |
... |     2 |     2 |
... 
... -----------------

OR

... -----------------
... | Pstart| Pstop |
... -----------------
... 
... |KEY(I) |KEY(I) |
... |KEY(I) |KEY(I) |
... 
... -----------------

这对此有何帮助?

不是很多。即使您提供的信息比典型问题多得多,但解决此问题还需要更多信息。

至少现在您知道问题不是由分区修剪的一般限制引起的。这里有一个非常具体的问题,很可能与优化器统计信息有关。了解这些问题可能需要大量时间。我建议从上面的示例数据开始,添加更多功能和数据,直到分区修剪消失为止。

通过修改问题在此处发布新的测试用例,并且有人应该能够解决它。