作用和使用方法
PUSH_SUBQ:尽可能早的执行不能做子查询展开的子查询,不能子查询展开时,通常最后执行,当子查询能早些过滤条件时,应该让其先执行。
NO_PUSH_SUBQ:不能展开时最后执行。
使用语法:
写在子查询中:SELECT /*+ PUSH_SUBQ */
写在外层:
select /*+ PUSH_SUBQ(@子查询代码块名称) */
from ...
where (子查询)
其中子查询代码块名称:也可以结合子查询qb_name定义代码块名称。
使用版本:ORACLE 10G及以上。
测试验证
创建测试表:
create table t1 as select * from dba_objects WHERE ROWNUM<=1000;
create table t2 as select * from dba_objects;
CREATE INDEX IDX_T2_ID ON t2(object_id);
create table t1_temp
as
select * from t1 where rownum<=100;
create table t1_temp_2
as
select * from t1 where rownum<=1;
exec dbms_stats.gather_table_stats(null,'t1');
exec dbms_stats.gather_table_stats(null,'t2');
exec dbms_stats.gather_table_stats(null,'t1_temp');
exec dbms_stats.gather_table_stats(null,'t1_temp_2');
测试SQL语句:
select t1.object_name,t2.object_name
from t1, t2
where t1.object_id = t2.object_id
and exists (select 1
from t1_temp tt
where (tt.object_id = t1.object_id
or tt.object_id = t1.data_object_id ) );
执行计划如下:
------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | Cost (%CPU)| A-Rows | A-Time | Buffers |
------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1854 (100)| 106 |00:00:00.15 | 4176 |
|* 1 | FILTER | | 1 | | | 106 |00:00:00.15 | 4176 |
|* 2 | HASH JOIN | | 1 | 1000 | 354 (1)| 1000 |00:00:00.05 | 1272 |
| 3 | TABLE ACCESS FULL| T1 | 1 | 1000 | 6 (0)| 1000 |00:00:00.01 | 15 |
| 4 | TABLE ACCESS FULL| T2 | 1 | 87194 | 348 (1)| 87194 |00:00:00.02 | 1257 |
|* 5 | TABLE ACCESS FULL | T1_TEMP | 1000 | 2 | 3 (0)| 106 |00:00:00.10 | 2904 |
------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter( IS NOT NULL)
2 - access("T1"."OBJECT_ID"="T2"."OBJECT_ID")
5 - filter(("TT"."OBJECT_ID"=:B1 OR "TT"."OBJECT_ID"=:B2))
由于子查询中使用了OR关联,不能做子查询展开。
因此将子查询的执行放在最后。
下面分析应用场景:
1.尽可能早执行:缩减数据量。
通过在子查询中使用Push_subq提示,目的是让子查询提前执行,达到缩减数据量的目的。
select t1.object_name,t2.object_name
from t1, t2
where t1.object_id = t2.object_id
and exists (select /*+ Push_subq */1
from t1_temp tt
where (tt.object_id = t1.object_id
or tt.object_id = t1.data_object_id ) );
调整后的执行计划如下:
-----------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | Cost (%CPU)| A-Rows | A-Time | Buffers |
-----------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 109 (100)| 106 |00:00:00.09 | 2964 |
| 1 | NESTED LOOPS | | 1 | 50 | 106 (0)| 106 |00:00:00.09 | 2964 |
| 2 | NESTED LOOPS | | 1 | 50 | 106 (0)| 106 |00:00:00.09 | 2952 |
|* 3 | TABLE ACCESS FULL | T1 | 1 | 50 | 6 (0)| 106 |00:00:00.09 | 2927 |
|* 4 | TABLE ACCESS FULL | T1_TEMP | 1000 | 2 | 3 (0)| 106 |00:00:00.09 | 2904 |
|* 5 | INDEX RANGE SCAN | IDX_T2_ID | 106 | 1 | 1 (0)| 106 |00:00:00.01 | 25 |
| 6 | TABLE ACCESS BY INDEX ROWID| T2 | 106 | 1 | 2 (0)| 106 |00:00:00.01 | 12 |
-----------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - filter( IS NOT NULL)
4 - filter(("TT"."OBJECT_ID"=:B1 OR "TT"."OBJECT_ID"=:B2))
5 - access("T1"."OBJECT_ID"="T2"."OBJECT_ID")
可以看到,子查询与T1表提前关联,可以提前缩减关联后的数据量到106行。
之后在与T2表关联时,由于数据量的减少,可以直接利用T2上的关联索引,提升查询效率,避免全表扫描的问题。
同理,可以使用另一种hint写法:
select /*+ Push_subq(@sub1) */t1.object_name,t2.object_name
from t1, t2
where t1.object_id = t2.object_id
and exists (select /*+ qb_name(sub1) */1
from t1_temp tt
where (tt.object_id = t1.object_id
or tt.object_id = t1.data_object_id ) );
如果使用其反义:no_Push_subq 。禁止子查询提前执行。
select t1.object_name,t2.object_name
from t1, t2
where t1.object_id = t2.object_id
and exists (select /*+ no_Push_subq */1
from t1_temp tt
where (tt.object_id = t1.object_id
or tt.object_id = t1.data_object_id ) );
则无法提前利用子查询缩减数据量。
------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | Cost (%CPU)| A-Rows | A-Time | Buffers |
------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1854 (100)| 106 |00:00:00.14 | 4176 |
|* 1 | FILTER | | 1 | | | 106 |00:00:00.14 | 4176 |
|* 2 | HASH JOIN | | 1 | 1000 | 354 (1)| 1000 |00:00:00.05 | 1272 |
| 3 | TABLE ACCESS FULL| T1 | 1 | 1000 | 6 (0)| 1000 |00:00:00.01 | 15 |
| 4 | TABLE ACCESS FULL| T2 | 1 | 87194 | 348 (1)| 87194 |00:00:00.02 | 1257 |
|* 5 | TABLE ACCESS FULL | T1_TEMP | 1000 | 2 | 3 (0)| 106 |00:00:00.09 | 2904 |
------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter( IS NOT NULL)
2 - access("T1"."OBJECT_ID"="T2"."OBJECT_ID")
5 - filter(("TT"."OBJECT_ID"=:B1 OR "TT"."OBJECT_ID"=:B2))
由于T1表数据较多,仍然需要全表访问T2表。
2.针对性的控制过滤性好的子查询先执行。
假设有两个子查询,但过滤效果不一样。t1_temp_2 表较小,过滤效果很好。
select t1.object_name,t2.object_name
from t1, t2
where t1.object_id = t2.object_id
and exists (select 1
from t1_temp tt
where (tt.object_id = t1.object_id
or tt.object_id = t1.data_object_id ) )
and exists (select 1
from t1_temp_2 tt2
where (tt2.object_id = t1.object_id
or tt2.object_id = t1.data_object_id ) );
默认情况下,数据库仍然会将子查询放在最后去执行。
--------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | Cost (%CPU)| A-Rows | A-Time | Buffers |
--------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1857 (100)| 1 |00:00:00.17 | 4381 |
|* 1 | FILTER | | 1 | | | 1 |00:00:00.17 | 4381 |
|* 2 | HASH JOIN | | 1 | 1000 | 354 (1)| 1000 |00:00:00.08 | 1265 |
| 3 | TABLE ACCESS FULL| T1 | 1 | 1000 | 6 (0)| 1000 |00:00:00.01 | 15 |
| 4 | TABLE ACCESS FULL| T2 | 1 | 87194 | 348 (1)| 87194 |00:00:00.04 | 1250 |
|* 5 | TABLE ACCESS FULL | T1_TEMP | 1000 | 2 | 3 (0)| 106 |00:00:00.09 | 2904 |
|* 6 | TABLE ACCESS FULL | T1_TEMP_2 | 106 | 1 | 3 (0)| 1 |00:00:00.01 | 212 |
--------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter(( IS NOT NULL AND IS NOT NULL))
2 - access("T1"."OBJECT_ID"="T2"."OBJECT_ID")
5 - filter(("TT"."OBJECT_ID"=:B1 OR "TT"."OBJECT_ID"=:B2))
6 - filter(("TT2"."OBJECT_ID"=:B1 OR "TT2"."OBJECT_ID"=:B2))
达不到提前缩减数据量的目的。
下面分别测试对两个子查询提前执行的效果。
select t1.object_name,t2.object_name
from t1, t2
where t1.object_id = t2.object_id
and exists (select /*+ Push_subq */1
from t1_temp tt
where tt.object_id = t1.object_id or tt.object_id = t1.data_object_id)
and exists (select 1
from t1_temp_2 tt2
where tt2.object_id = t1.object_id or tt2.object_id = t1.data_object_id);
------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | Cost (%CPU)| A-Rows | A-Time | Buffers |
------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 184 (100)| 1 |00:00:00.09 | 3149 |
|* 1 | FILTER | | 1 | | | 1 |00:00:00.09 | 3149 |
| 2 | NESTED LOOPS | | 1 | 50 | 106 (0)| 106 |00:00:00.09 | 2937 |
| 3 | NESTED LOOPS | | 1 | 50 | 106 (0)| 106 |00:00:00.09 | 2931 |
|* 4 | TABLE ACCESS FULL | T1 | 1 | 50 | 6 (0)| 106 |00:00:00.09 | 2920 |
|* 5 | TABLE ACCESS FULL | T1_TEMP | 1000 | 2 | 3 (0)| 106 |00:00:00.09 | 2904 |
|* 6 | INDEX RANGE SCAN | IDX_T2_ID | 106 | 1 | 1 (0)| 106 |00:00:00.01 | 11 |
| 7 | TABLE ACCESS BY INDEX ROWID| T2 | 106 | 1 | 2 (0)| 106 |00:00:00.01 | 6 |
|* 8 | TABLE ACCESS FULL | T1_TEMP_2 | 106 | 1 | 3 (0)| 1 |00:00:00.01 | 212 |
------------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter( IS NOT NULL)
4 - filter( IS NOT NULL)
5 - filter(("TT"."OBJECT_ID"=:B1 OR "TT"."OBJECT_ID"=:B2))
6 - access("T1"."OBJECT_ID"="T2"."OBJECT_ID")
8 - filter(("TT2"."OBJECT_ID"=:B1 OR "TT2"."OBJECT_ID"=:B2))
提前执行T1_TEMP子查询,将数据量缩减到106行。之后循环106次关联T2表。
select t1.object_name,t2.object_name
from t1, t2
where t1.object_id = t2.object_id
and exists (select 1
from t1_temp tt
where tt.object_id = t1.object_id or tt.object_id = t1.data_object_id)
and exists (select /*+ Push_subq */1
from t1_temp_2 tt2
where tt2.object_id = t1.object_id or tt2.object_id = t1.data_object_id);
------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | Cost (%CPU)| A-Rows | A-Time | Buffers |
------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 184 (100)| 1 |00:00:00.01 | 2022 |
|* 1 | FILTER | | 1 | | | 1 |00:00:00.01 | 2022 |
| 2 | NESTED LOOPS | | 1 | 50 | 106 (0)| 1 |00:00:00.01 | 2020 |
| 3 | NESTED LOOPS | | 1 | 50 | 106 (0)| 1 |00:00:00.01 | 2019 |
|* 4 | TABLE ACCESS FULL | T1 | 1 | 50 | 6 (0)| 1 |00:00:00.01 | 2016 |
|* 5 | TABLE ACCESS FULL | T1_TEMP_2 | 1000 | 1 | 3 (0)| 1 |00:00:00.01 | 2000 |
|* 6 | INDEX RANGE SCAN | IDX_T2_ID | 1 | 1 | 1 (0)| 1 |00:00:00.01 | 3 |
| 7 | TABLE ACCESS BY INDEX ROWID| T2 | 1 | 1 | 2 (0)| 1 |00:00:00.01 | 1 |
|* 8 | TABLE ACCESS FULL | T1_TEMP | 1 | 2 | 3 (0)| 1 |00:00:00.01 | 2 |
------------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter( IS NOT NULL)
4 - filter( IS NOT NULL)
5 - filter(("TT2"."OBJECT_ID"=:B1 OR "TT2"."OBJECT_ID"=:B2))
6 - access("T1"."OBJECT_ID"="T2"."OBJECT_ID")
8 - filter(("TT"."OBJECT_ID"=:B1 OR "TT"."OBJECT_ID"=:B2))
提前执行T1_TEMP_2子查询,将数据量缩减到1行。之后只需要循环访问1次T2表。执行效率较好。
因此,可以有针对过滤性较好的子查询先执行,
通过关联子查询降低数据量,提升查询效率。
3.不提前执行时,按照子查询出现顺序访问子查询。
select t1.object_name,t2.object_name
from t1, t2
where t1.object_id = t2.object_id
and exists (select 1
from t1_temp tt
where tt.object_id = t1.object_id or tt.object_id = t1.data_object_id)
and exists (select 1
from t1_temp_2 tt2
where tt2.object_id = t1.object_id or tt2.object_id = t1.data_object_id);
--------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | Cost (%CPU)| A-Rows | A-Time | Buffers |
--------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1857 (100)| 1 |00:00:00.16 | 4381 |
|* 1 | FILTER | | 1 | | | 1 |00:00:00.16 | 4381 |
|* 2 | HASH JOIN | | 1 | 1000 | 354 (1)| 1000 |00:00:00.07 | 1265 |
| 3 | TABLE ACCESS FULL| T1 | 1 | 1000 | 6 (0)| 1000 |00:00:00.01 | 15 |
| 4 | TABLE ACCESS FULL| T2 | 1 | 87194 | 348 (1)| 87194 |00:00:00.03 | 1250 |
|* 5 | TABLE ACCESS FULL | T1_TEMP | 1000 | 2 | 3 (0)| 106 |00:00:00.09 | 2904 |
|* 6 | TABLE ACCESS FULL | T1_TEMP_2 | 106 | 1 | 3 (0)| 1 |00:00:00.01 | 212 |
--------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter(( IS NOT NULL AND IS NOT NULL))
2 - access("T1"."OBJECT_ID"="T2"."OBJECT_ID")
5 - filter(("TT"."OBJECT_ID"=:B1 OR "TT"."OBJECT_ID"=:B2))
6 - filter(("TT2"."OBJECT_ID"=:B1 OR "TT2"."OBJECT_ID"=:B2))
默认情况下,会按照子查询出现顺序访问。
修改出现顺序:
select t1.object_name,t2.object_name
from t1, t2
where t1.object_id = t2.object_id
and exists (select 1
from t1_temp_2 tt2
where tt2.object_id = t1.object_id or tt2.object_id = t1.data_object_id)
and exists (select 1
from t1_temp tt
where tt.object_id = t1.object_id or tt.object_id = t1.data_object_id);
--------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | Cost (%CPU)| A-Rows | A-Time | Buffers |
--------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1857 (100)| 1 |00:00:00.08 | 3267 |
|* 1 | FILTER | | 1 | | | 1 |00:00:00.08 | 3267 |
|* 2 | HASH JOIN | | 1 | 1000 | 354 (1)| 1000 |00:00:00.08 | 1265 |
| 3 | TABLE ACCESS FULL| T1 | 1 | 1000 | 6 (0)| 1000 |00:00:00.01 | 15 |
| 4 | TABLE ACCESS FULL| T2 | 1 | 87194 | 348 (1)| 87194 |00:00:00.04 | 1250 |
|* 5 | TABLE ACCESS FULL | T1_TEMP_2 | 1000 | 1 | 3 (0)| 1 |00:00:00.01 | 2000 |
|* 6 | TABLE ACCESS FULL | T1_TEMP | 1 | 2 | 3 (0)| 1 |00:00:00.01 | 2 |
--------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter(( IS NOT NULL AND IS NOT NULL))
2 - access("T1"."OBJECT_ID"="T2"."OBJECT_ID")
5 - filter(("TT2"."OBJECT_ID"=:B1 OR "TT2"."OBJECT_ID"=:B2))
6 - filter(("TT"."OBJECT_ID"=:B1 OR "TT"."OBJECT_ID"=:B2))
不同的出现顺序,也会影响先后执行顺序。最终影响过滤效果,导致执行效率差异。
可以有针对性的控制过滤性好的子查询先书写。来达到缩减数据量的目的。
知识总结
PUSH_SUBQ可以让子查询尽可能提前执行,在分析子查询时,如果能确定其具有很好的过滤作用,可以利用该提示优先执行子查询,来测试是否能提升执行效率。