我有一个表在数字列(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子句进行分区时?
答案 0 :(得分:1)
Oracle 11g的答案是肯定的,分区修剪工作正常。
您的设置中有三种主要的访问模式,list partitioned
表TABLEA
,让我们全部通过。请注意,我正在使用
最简单的陈述来说明行为。
使用相等谓词或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) - 请参阅Pstart
和Pstop
列。
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@!)
请参阅Pstart
,Pstop
:BFnnnn
作为Bloom过滤器的标志。
答案 1 :(得分:0)
分区修剪可以使用LEFT OUTER JOIN
和IN
子查询。您可能会看到一个非常具体的问题,需要更具体的测试用例。
示例架构
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);
<强>输出强>
此处不显示完整的执行计划,以节省空间。唯一重要的列是Pstart
和Pstop
,这意味着使用分区修剪。
执行计划看起来像下列之一:
... -----------------
... | Pstart| Pstop |
... -----------------
...
... | KEY | KEY |
... | KEY | KEY |
...
... -----------------
OR
... -----------------
... | Pstart| Pstop |
... -----------------
...
... | 2 | 2 |
... | 2 | 2 |
...
... -----------------
OR
... -----------------
... | Pstart| Pstop |
... -----------------
...
... |KEY(I) |KEY(I) |
... |KEY(I) |KEY(I) |
...
... -----------------
这对此有何帮助?
不是很多。即使您提供的信息比典型问题多得多,但解决此问题还需要更多信息。
至少现在您知道问题不是由分区修剪的一般限制引起的。这里有一个非常具体的问题,很可能与优化器统计信息有关。了解这些问题可能需要大量时间。我建议从上面的示例数据开始,添加更多功能和数据,直到分区修剪消失为止。
通过修改问题在此处发布新的测试用例,并且有人应该能够解决它。