12c Adaptive Plans

12c在优化器方面引入了不少的新特性,其中一个就是Adaptive Plans

在特定的条件下优化器会使用这个新特性,比如sql语句包含了表关联、复杂的谓词条件导致很难精准的估算cardinality,Adaptive Plans使优化器可以等到真正在执行sql语句的时候才决定采用哪种执行计划。

优化器会优先选择一个默认执行计划,同时搭配一个_statistics collectors_,这样就能发现实际的cardinality和估算的cardinality之间是否有比较大的区别,如果有明显区别,那么Adaptive Plans这个新特性就能自动的去选择更优的执行计划。

Adaptive Join Methods

优化器能动态调整连接方法,为整个plan的多个部分预先设定多个子plan。

例如上图,优化器默认选择的计划是PRODUCTS表走索引扫描,然后与ORDERS全表扫描的结果集做嵌套循环,但同时优化器也提供了另一种关联方式,就是两个表都走全表扫描,然后做hash join,然后具体执行的时候该选择那一种就是一个比较复杂的判断了。

sql语句在开始执行之前,statistics collectors会收集必要的信息来提供给下一步的执行计划,而具体要收集哪些信息则是有优化器来决定。首先优化器会计算出一个特定的拐点,作为各个子plan的判断依据。比如假设ORDERS表返回的结果集小于10,这个时候nested loop的COST更优,而结果集大于10时,hash join的COST更好,那么优化器就计算出10这个值作为拐点。优化器之后就会让statistics collectors去统计返回值,如果扫描到10行以上,则从NESTED LOOPS切换到HASH JOIN,否则则维持NESTED LOOPS。statistics collectors主要的作用就是在ORDERS表做全表扫描的过程中进行监控和缓存行,优化器根据statistics collectors得到的信息来决定使用哪个执行计划。

先建两张表

1
2
3
4
5
6
7
8
9
10
11
12
13
drop table t1 purge;
drop table t2 purge;

create table t1 as select object_id ,object_name,owner from dba_objects where object_id is not null;
alter table t1 add primary key(object_id);
create index idx_t1 on t1(object_name);

create table t2 as select object_id ,object_name,owner from dba_objects where object_id is not null;
alter table t2 add primary key(object_id);


EXEC DBMS_STATS.gather_table_stats(USER, 'T1');
EXEC DBMS_STATS.gather_table_stats(USER, 'T2');

做一个两表关联的sql,看执行计划会是什么样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
select t1.owner ,t2.owner 
from t1,t2
where t1.object_id=t2.object_id
and t1.object_name='TS$';

-------------------------------------------------------------------------------------------
| Id | Operation | Name | E-Rows |E-Bytes| Cost (%CPU)|
-------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 5 (100)|
| 1 | NESTED LOOPS | | 1 | 55 | 5 (0)|
| 2 | NESTED LOOPS | | 1 | 55 | 5 (0)|
| 3 | TABLE ACCESS BY INDEX ROWID BATCHED| T1 | 1 | 45 | 4 (0)|
|* 4 | INDEX RANGE SCAN | IDX_T1 | 1 | | 3 (0)|
|* 5 | INDEX UNIQUE SCAN | SYS_C007364 | 1 | | 0 (0)|
| 6 | TABLE ACCESS BY INDEX ROWID | T2 | 1 | 10 | 1 (0)|
-------------------------------------------------------------------------------------------

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

4 - access("T1"."OBJECT_NAME"='TS$')
5 - access("T1"."OBJECT_ID"="T2"."OBJECT_ID")

- this is an adaptive plan >>>>====表示自适应执行计划

可以通过DBMS_XPLAN.DISPLAY_CURSOR的新参数看到一个自适应计划的每一个步骤

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
SYS@ora12c> SELECT * FROM TABLE(DBMS_XPLAN.display_cursor(format => 'adaptive'));

Plan hash value: 970098525

---------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 5 (100)| |
|- * 1 | HASH JOIN | | 1 | 55 | 5 (0)| 00:00:01 |
| 2 | NESTED LOOPS | | 1 | 55 | 5 (0)| 00:00:01 |
| 3 | NESTED LOOPS | | 1 | 55 | 5 (0)| 00:00:01 |
|- 4 | STATISTICS COLLECTOR | | | | | |
| 5 | TABLE ACCESS BY INDEX ROWID BATCHED| T1 | 1 | 45 | 4 (0)| 00:00:01 |
| * 6 | INDEX RANGE SCAN | IDX_T1 | 1 | | 3 (0)| 00:00:01 |
| * 7 | INDEX UNIQUE SCAN | SYS_C007364 | 1 | | 0 (0)| |
| 8 | TABLE ACCESS BY INDEX ROWID | T2 | 1 | 10 | 1 (0)| 00:00:01 |
|- 9 | TABLE ACCESS FULL | T2 | 1 | 10 | 1 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------------------

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

1 - access("T1"."OBJECT_ID"="T2"."OBJECT_ID")
6 - access("T1"."OBJECT_NAME"='TS$')
7 - access("T1"."OBJECT_ID"="T2"."OBJECT_ID")

Note
-----
- this is an adaptive plan (rows marked '-' are inactive)

每个ID前面带有’-‘符号的表示这个操作并没有被执行计划所采用。

为了得到真实的执行计划,通常建议采用/*+ GATHER_PLAN_STATISTICS */ hint的方式来执行语句,然后通过allstats last的方式来查询结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
select /*+ GATHER_PLAN_STATISTICS */t1.owner ,t2.owner 
from t1,t2
where t1.object_id=t2.object_id
and t1.object_name='TS$';

SYS@ora12c> SELECT * FROM TABLE(DBMS_XPLAN.display_cursor(format => 'adaptive allstats last'));

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID 49n5jxqsqksss, child number 0
-------------------------------------
select /*+ GATHER_PLAN_STATISTICS */t1.owner ,t2.owner from t1,t2
where t1.object_id=t2.object_id and t1.object_name='TS$'

Plan hash value: 970098525

-------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.01 | 7 |
|- * 1 | HASH JOIN | | 1 | 1 | 1 |00:00:00.01 | 7 |
| 2 | NESTED LOOPS | | 1 | 1 | 1 |00:00:00.01 | 7 |
| 3 | NESTED LOOPS | | 1 | 1 | 1 |00:00:00.01 | 6 |
|- 4 | STATISTICS COLLECTOR | | 1 | | 1 |00:00:00.01 | 4 |
| 5 | TABLE ACCESS BY INDEX ROWID BATCHED| T1 | 1 | 1 | 1 |00:00:00.01 | 4 |
| * 6 | INDEX RANGE SCAN | IDX_T1 | 1 | 1 | 1 |00:00:00.01 | 3 |
| * 7 | INDEX UNIQUE SCAN | SYS_C007364 | 1 | 1 | 1 |00:00:00.01 | 2 |
| 8 | TABLE ACCESS BY INDEX ROWID | T2 | 1 | 1 | 1 |00:00:00.01 | 1 |
|- 9 | TABLE ACCESS FULL | T2 | 0 | 1 | 0 |00:00:00.01 | 0 |
-------------------------------------------------------------------------------------------------------------------

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

1 - access("T1"."OBJECT_ID"="T2"."OBJECT_ID")
6 - access("T1"."OBJECT_NAME"='TS$')
7 - access("T1"."OBJECT_ID"="T2"."OBJECT_ID")

Note
-----
- this is an adaptive plan (rows marked '-' are inactive)


33 rows selected.

现在对表中的数据做一些处理,看执行计划是否能自适应变更

1
2
3
4
5
6
7
8
9
update t1
set object_name='TS$'
where rownum>60000;
commit;

59999 rows updated.

SYS@ora12c>
Commit complete.

现在T1表的查询条件满足的值不再是一条,而是60000条,这里我并没有重新收集统计信息,看最新的执行计划会是怎样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
SQL_ID  49n5jxqsqksss, child number 0
-------------------------------------
select /*+ GATHER_PLAN_STATISTICS */t1.owner ,t2.owner from t1,t2
where t1.object_id=t2.object_id and t1.object_name='TS$'

Plan hash value: 970098525

-------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 59999 |00:00:00.15 | 62438 |
|- * 1 | HASH JOIN | | 1 | 1 | 59999 |00:00:00.15 | 62438 |
| 2 | NESTED LOOPS | | 1 | 1 | 59999 |00:00:00.13 | 62438 |
| 3 | NESTED LOOPS | | 1 | 1 | 59999 |00:00:00.13 | 2439 |
|- 4 | STATISTICS COLLECTOR | | 1 | | 59999 |00:00:00.08 | 1094 |
| 5 | TABLE ACCESS BY INDEX ROWID BATCHED| T1 | 1 | 1 | 59999 |00:00:00.03 | 1094 |
| * 6 | INDEX RANGE SCAN | IDX_T1 | 1 | 1 | 59999 |00:00:00.06 | 565 |
| * 7 | INDEX UNIQUE SCAN | SYS_C007364 | 59999 | 1 | 59999 |00:00:00.02 | 1345 |
| 8 | TABLE ACCESS BY INDEX ROWID | T2 | 59999 | 1 | 59999 |00:00:00.03 | 59999 |
|- 9 | TABLE ACCESS FULL | T2 | 0 | 1 | 0 |00:00:00.01 | 0 |
-------------------------------------------------------------------------------------------------------------------

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

1 - access("T1"."OBJECT_ID"="T2"."OBJECT_ID")
6 - access("T1"."OBJECT_NAME"='TS$')
7 - access("T1"."OBJECT_ID"="T2"."OBJECT_ID")

Note
-----
- this is an adaptive plan (rows marked '-' are inactive)

可以看到重新执行以后,执行计划并未有什么改变,这是由于最终的执行计划是由第一次运行时所决定了

清除shared pool,强制硬解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
SYS@ora12c> alter system flush shared_pool
2 ;

System altered.

SYS@ora12c> @1
SYS@ora12c> SELECT * FROM TABLE(DBMS_XPLAN.display_cursor(format => 'allstats last adaptive'));

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID 49n5jxqsqksss, child number 0
-------------------------------------
select /*+ GATHER_PLAN_STATISTICS */t1.owner ,t2.owner from t1,t2
where t1.object_id=t2.object_id and t1.object_name='TS$'

Plan hash value: 925498821

----------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | OMem | 1Mem | Used-Mem |
----------------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 59999 |00:00:00.08 | 1268 | | | |
| * 1 | HASH JOIN | | 1 | 1 | 59999 |00:00:00.08 | 1268 | 4515K| 2259K| 4429K (0)|
|- 2 | NESTED LOOPS | | 1 | 1 | 59999 |00:00:00.04 | 635 | | | |
|- 3 | NESTED LOOPS | | 1 | 1 | 59999 |00:00:00.04 | 635 | | | |
|- 4 | STATISTICS COLLECTOR | | 1 | | 59999 |00:00:00.03 | 635 | | | |
| 5 | TABLE ACCESS BY INDEX ROWID BATCHED| T1 | 1 | 1 | 59999 |00:00:00.02 | 635 | | | |
| * 6 | INDEX RANGE SCAN | IDX_T1 | 1 | 1 | 59999 |00:00:00.01 | 224 | | | |
|- * 7 | INDEX UNIQUE SCAN | SYS_C007364 | 0 | 1 | 0 |00:00:00.01 | 0 | | | |
|- 8 | TABLE ACCESS BY INDEX ROWID | T2 | 0 | 1 | 0 |00:00:00.01 | 0 | | | |
| 9 | TABLE ACCESS FULL | T2 | 1 | 1 | 72661 |00:00:00.01 | 633 | | | |
----------------------------------------------------------------------------------------------------------------------------------------------

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

1 - access("T1"."OBJECT_ID"="T2"."OBJECT_ID")
6 - access("T1"."OBJECT_NAME"='TS$')
7 - access("T1"."OBJECT_ID"="T2"."OBJECT_ID")

Note
-----
- this is an adaptive plan (rows marked '-' are inactive)


33 rows selected.

最终的结果显示完整的执行计划并没有区别,由于硬解析过程中_STATISTICS COLLECTOR_重新收集了数据,所以根据新的统计信息优化器选择了更优的子计划,用hash join代替了之前的nested loop。

Adaptive Parallel Distribution Method

当一个sql语句并发执行时,例如排序、分组或者关联时需要数据分布在多个不同的并发子进程中。优化器选择哪种分布操作取决于有多少并发子进程,多少行数据。如果优化器无法准确的估算出有多少数据,那就不能得到最优的分布操作。

由于新的自适应分布操作特性(HYBRID HASH),优化器可以直到在最终执行sql时才决定采用哪种分布操作,同理会先采用_STATISTICS COLLECTOR_收集一波需要缓存的数据信息,根据得到的rows信息来决定分布操作。同样会计算出一个拐点,根据收集到的值与拐点最比较,最终确定分布操作是用HASH还是广播。跟Adaptive JOIN METHOD只影响第一次执行不同的是,Adaptive Parallel Distribution Method影响sql的每一次执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
EXEC DBMS_STATS.gather_table_stats(USER, 'T1');
EXEC DBMS_STATS.gather_table_stats(USER, 'T2');

SYS@ora12c> alter system flush shared_pool;

System altered.

select /*+ GATHER_PLAN_STATISTICS PARALLEL(8)*/t1.owner ,t2.owner
from t1,t2
where t1.object_id=t2.object_id
and t1.object_name='TS$';

SYS@ora12c> SELECT * FROM TABLE(DBMS_XPLAN.display_cursor(format => 'allstats last adaptive'));

SQL_ID gjm4v4t32wamn, child number 0
-------------------------------------
select /*+ GATHER_PLAN_STATISTICS PARALLEL(8)*/t1.owner ,t2.owner from
t1,t2 where t1.object_id=t2.object_id and t1.object_name='TS$'

Plan hash value: 435755347

--------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | OMem | 1Mem | Used-Mem | Used-Tmp|
--------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 59999 |00:00:00.44 | 40 | | | | |
| 1 | PX COORDINATOR | | 1 | | 59999 |00:00:00.44 | 40 | 73728 | 73728 | | |
| 2 | PX SEND QC (RANDOM) | :TQ10002 | 0 | 60537 | 0 |00:00:00.01 | 0 | | | | |
|* 3 | HASH JOIN BUFFERED | | 0 | 60537 | 0 |00:00:00.01 | 0 | 7106K| 3091K| 4581K (1)| 1024 |
| 4 | PX RECEIVE | | 0 | 72661 | 0 |00:00:00.01 | 0 | | | | |
| 5 | PX SEND HYBRID HASH | :TQ10000 | 0 | 72661 | 0 |00:00:00.01 | 0 | | | | | >>>>====新的HYBRID HASH分布操作
| 6 | STATISTICS COLLECTOR | | 0 | | 0 |00:00:00.01 | 0 | | | | |
| 7 | PX BLOCK ITERATOR | | 0 | 72661 | 0 |00:00:00.01 | 0 | | | | |
|* 8 | TABLE ACCESS FULL | T2 | 0 | 72661 | 0 |00:00:00.01 | 0 | | | | |
| 9 | PX RECEIVE | | 0 | 60537 | 0 |00:00:00.01 | 0 | | | | |
| 10 | PX SEND HYBRID HASH | :TQ10001 | 0 | 60537 | 0 |00:00:00.01 | 0 | | | | | >>>>====新的HYBRID HASH分布操作
| 11 | PX BLOCK ITERATOR | | 0 | 60537 | 0 |00:00:00.01 | 0 | | | | |
|* 12 | TABLE ACCESS FULL | T1 | 0 | 60537 | 0 |00:00:00.01 | 0 | | | | |
--------------------------------------------------------------------------------------------------------------------------------------

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

3 - access("T1"."OBJECT_ID"="T2"."OBJECT_ID")
8 - access(:Z>=:Z AND :Z>=:Z)
12 - access(:Z>=:Z AND :Z>=:Z)
filter("T1"."OBJECT_NAME"='TS$')

Note
-----
- Degree of Parallelism is 8 because of hint

Adaptive Bitmap Index Pruning

当优化器生成一个星型转换的执行计划时,必须要选择正确的位图索引结合方式,用以减少访问相关的ROWIDS。如果访问了太多索引,而部分索引又不能很好的过滤数据,那么减少访问的索引量无疑能增加执行效率。Adaptive Bitmap Index Pruning这个特性就是用来自动做这些索引的裁剪工作。

先构建一个星型转换的场景

新建一张事实表和三张维度表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
create table DIM1 (dim1_id number,DIM1_COD varchar2(10),DIM1_TXT varchar2(10));
insert into DIM1 values(1,'One','One');
insert into DIM1 values(2,'Two','Two');
insert into DIM1 values(3,'Three','Three');
insert into DIM1 values(4,'Four','Four');
insert into DIM1 values(5,'Five','Five');
insert into DIM1 values(6,'Six','Six');
insert into DIM1 values(7,'Seven','Seven');
insert into DIM1 values(8,'Eight','Eight');
insert into DIM1 values(9,'Nine','Nine');
insert into DIM1 values(10,'Ten','Ten');
commit;

create table DIM2 as select DIM1_ID DIM2_ID,DIM1_COD DIM2_COD,DIM1_TXT DIM2_TXT from DIM1 where rownum>=10;
create table DIM3 as select DIM1_ID DIM3_ID,DIM1_COD DIM3_COD,DIM1_TXT DIM3_TXT from DIM1 where rownum>=10;

create table FACT as select rownum FACT_ID,DIM1_ID,DIM2_ID,DIM3_ID,mod(rownum,1000)/10 FACT_MESURE from
DIM1,DIM2,DIM3,(select * from dual connect by level>=1000);

alter table DIM1 add constraint DIM1PK primary key(DIM1_ID);
alter table FACT add constraint DIM1FK foreign key (DIM1_ID) references DIM1;
create index DIM1BX on FACT(DIM1_ID);
alter table DIM2 add constraint DIM2PK primary key(DIM2_ID);
alter table FACT add constraint DIM2FK foreign key (DIM2_ID) references DIM2;
create index DIM2BX on FACT(DIM2_ID);
alter table DIM3 add constraint DIM3PK primary key(DIM3_ID);
alter table FACT add constraint DIM3FK foreign key (DIM3_ID) references DIM3;
create index DIM3BX on FACT(DIM3_ID);

SYS@ora12c> alter session set star_transformation_enabled=true;

Session altered.

收集相关统计信息

1
2
3
4
exec DBMS_STATS.gather_table_stats(USER, 'DIM1');
exec DBMS_STATS.gather_table_stats(USER, 'DIM2');
exec DBMS_STATS.gather_table_stats(USER, 'DIM3');
exec DBMS_STATS.gather_table_stats(USER, 'FACT');

当维度表的数据很少时,如果索引的选择性又很好,那么星型转换的效率很高。如下结果:首先对3张维度表进行了扫描(每张表都应用了对应的谓词条件),对三个结果集进行了笛卡尔积关联得到满足所有谓词的数据,得到的结果集包含用于访问fact表的维度键以及得到最后结果所需要的相关信息。对于每一个维度键扫描bitmap index得到相应的行,这些bitmaps随后合并到一起,做完and运算后转换成ROWID后找到对应fact表。这里statistics collectors的目的主要是提供缓存的数据信息,来确定之后是用nested loop还是其他。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
SQL_ID  4dk2udqhwz501, child number 0
-------------------------------------
select /*+ GATHER_PLAN_STATISTICS */* from FACT join DIM1
using(DIM1_ID) join DIM2 using(DIM2_ID) join DIM3 using(DIM3_ID) where
DIM1_COD='One' and DIM2_COD='One' and DIM3_COD='One'

Plan hash value: 246786650

-----------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | OMem | 1Mem | Used-Mem |
-----------------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1000 |00:00:00.04 | 1636 | | | |
| * 1 | HASH JOIN | | 1 | 1000 | 1000 |00:00:00.04 | 1636 | 1123K| 1123K| 414K (0)|
| 2 | MERGE JOIN CARTESIAN | | 1 | 1 | 1 |00:00:00.01 | 21 | | | |
| 3 | MERGE JOIN CARTESIAN | | 1 | 1 | 1 |00:00:00.01 | 12 | | | |
| * 4 | TABLE ACCESS FULL | DIM1 | 1 | 1 | 1 |00:00:00.01 | 3 | | | |
| 5 | BUFFER SORT | | 1 | 1 | 1 |00:00:00.01 | 9 | 2048 | 2048 | 2048 (0)|
| * 6 | TABLE ACCESS FULL | DIM2 | 1 | 1 | 1 |00:00:00.01 | 9 | | | |
| 7 | BUFFER SORT | | 1 | 1 | 1 |00:00:00.01 | 9 | 2048 | 2048 | 2048 (0)|
| * 8 | TABLE ACCESS FULL | DIM3 | 1 | 1 | 1 |00:00:00.01 | 9 | | | |
| 9 | VIEW | VW_ST_A500F760 | 1 | 1000 | 1000 |00:00:00.04 | 1615 | | | |
| 10 | NESTED LOOPS | | 1 | 1000 | 1000 |00:00:00.04 | 1615 | | | |
| 11 | BITMAP CONVERSION TO ROWIDS | | 1 | 1000 | 1000 |00:00:00.04 | 615 | | | |
| 12 | BITMAP AND | | 1 | | 1 |00:00:00.04 | 615 | | | |
| 13 | BITMAP MERGE | | 1 | | 1 |00:00:00.01 | 201 | 1024K| 512K|20480 (0)|
| 14 | BITMAP KEY ITERATION | | 1 | | 1 |00:00:00.01 | 201 | | | |
| * 15 | TABLE ACCESS FULL | DIM1 | 1 | 1 | 1 |00:00:00.01 | 3 | | | |
| 16 | BITMAP CONVERSION FROM ROWIDS | | 1 | | 1 |00:00:00.01 | 198 | | | |
| * 17 | INDEX RANGE SCAN | DIM1BX | 1 | | 100K|00:00:00.07 | 198 | | | |
|- 18 | STATISTICS COLLECTOR | | 1 | | 2 |00:00:00.01 | 207 | | | |
| 19 | BITMAP MERGE | | 1 | | 2 |00:00:00.01 | 207 | 1024K| 512K|47104 (0)|
| 20 | BITMAP KEY ITERATION | | 1 | | 2 |00:00:00.01 | 207 | | | |
| * 21 | TABLE ACCESS FULL | DIM2 | 1 | 1 | 1 |00:00:00.01 | 9 | | | |
| 22 | BITMAP CONVERSION FROM ROWIDS| | 1 | | 2 |00:00:00.01 | 198 | | | |
| * 23 | INDEX RANGE SCAN | DIM2BX | 1 | | 100K|00:00:00.06 | 198 | | | |
|- 24 | STATISTICS COLLECTOR | | 1 | | 5 |00:00:00.01 | 207 | | | |
| 25 | BITMAP MERGE | | 1 | | 5 |00:00:00.01 | 207 | 1024K| 512K| 146K (0)|
| 26 | BITMAP KEY ITERATION | | 1 | | 5 |00:00:00.01 | 207 | | | |
| * 27 | TABLE ACCESS FULL | DIM3 | 1 | 1 | 1 |00:00:00.01 | 9 | | | |
| 28 | BITMAP CONVERSION FROM ROWIDS| | 1 | | 5 |00:00:00.01 | 198 | | | |
| * 29 | INDEX RANGE SCAN | DIM3BX | 1 | | 100K|00:00:00.06 | 198 | | | |
| 30 | TABLE ACCESS BY USER ROWID | FACT | 1000 | 1 | 1000 |00:00:00.01 | 1000 | | | |
-----------------------------------------------------------------------------------------------------------------------------------------------

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

1 - access("ITEM_3"="DIM3"."DIM3_ID" AND "ITEM_2"="DIM2"."DIM2_ID" AND "ITEM_1"="DIM1"."DIM1_ID")
4 - filter("DIM1"."DIM1_COD"='One')
6 - filter("DIM2"."DIM2_COD"='One')
8 - filter("DIM3"."DIM3_COD"='One')
15 - filter("DIM1"."DIM1_COD"='One')
17 - access("FACT"."DIM1_ID"="DIM1"."DIM1_ID")
21 - filter("DIM2"."DIM2_COD"='One')
23 - access("FACT"."DIM2_ID"="DIM2"."DIM2_ID")
27 - filter("DIM3"."DIM3_COD"='One')
29 - access("FACT"."DIM3_ID"="DIM3"."DIM3_ID")

Note
-----
- star transformation used for this statement
- this is an adaptive plan (rows marked '-' are inactive)

假设表记录估算错误,导致维度表不能很好的过滤掉数据,fact表中大部分的数据都满足维度表的要求,那么优化器会直接先忽略掉那个维度表的条件,先做其他纬度表的bitmap合并操作,等到最后再与这个表做必要的关联。

现在对数据做下处理,更改一下DIM3表的数据,使其所有的数据都满足谓词条件

1
2
3
4
5
6
7
8
9
update DIM3
set DIM3_COD='One';


10 rows updated.

SYS@ora12c> SYS@ora12c> commit;

Commit complete.

重新查看新的执行计划

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
SYS@ora12c> alter system flush shared_pool;

System altered.

SYS@ora12c> @1
SYS@ora12c> SELECT * FROM TABLE(DBMS_XPLAN.display_cursor(format => 'allstats last adaptive'));

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID 4dk2udqhwz501, child number 0
-------------------------------------
select /*+ GATHER_PLAN_STATISTICS */* from FACT join DIM1
using(DIM1_ID) join DIM2 using(DIM2_ID) join DIM3 using(DIM3_ID) where
DIM1_COD='One' and DIM2_COD='One' and DIM3_COD='One'

Plan hash value: 788957811

--------------------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | Reads | OMem | 1Mem | Used-Mem |
--------------------------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 10000 |00:00:00.18 | 3457 | 3 | | | |
| * 1 | HASH JOIN | | 1 | 1000 | 10000 |00:00:00.18 | 3457 | 3 | 1123K| 1123K| 921K (0)|
| 2 | MERGE JOIN CARTESIAN | | 1 | 1 | 10 |00:00:00.01 | 21 | 0 | | | |
| 3 | MERGE JOIN CARTESIAN | | 1 | 1 | 1 |00:00:00.01 | 12 | 0 | | | |
| * 4 | TABLE ACCESS FULL | DIM1 | 1 | 1 | 1 |00:00:00.01 | 3 | 0 | | | |
| 5 | BUFFER SORT | | 1 | 1 | 1 |00:00:00.01 | 9 | 0 | 2048 | 2048 | 2048 (0)|
| * 6 | TABLE ACCESS FULL | DIM2 | 1 | 1 | 1 |00:00:00.01 | 9 | 0 | | | |
| 7 | BUFFER SORT | | 1 | 1 | 10 |00:00:00.01 | 9 | 0 | 2048 | 2048 | 2048 (0)|
| * 8 | TABLE ACCESS FULL | DIM3 | 1 | 1 | 10 |00:00:00.01 | 9 | 0 | | | |
| 9 | VIEW | VW_ST_A500F760 | 1 | 1000 | 10000 |00:00:00.18 | 3436 | 3 | | | |
| 10 | NESTED LOOPS | | 1 | 1000 | 10000 |00:00:00.18 | 3436 | 3 | | | |
| 11 | BITMAP CONVERSION TO ROWIDS | | 1 | 1000 | 10000 |00:00:00.15 | 2392 | 3 | | | |
| 12 | BITMAP AND | | 1 | | 1 |00:00:00.15 | 2392 | 3 | | | |
| 13 | BITMAP MERGE | | 1 | | 1 |00:00:00.01 | 201 | 0 | 1024K| 512K|20480 (0)|
| 14 | BITMAP KEY ITERATION | | 1 | | 1 |00:00:00.01 | 201 | 0 | | | |
| * 15 | TABLE ACCESS FULL | DIM1 | 1 | 1 | 1 |00:00:00.01 | 3 | 0 | | | |
| 16 | BITMAP CONVERSION FROM ROWIDS | | 1 | | 1 |00:00:00.01 | 198 | 0 | | | |
| * 17 | INDEX RANGE SCAN | DIM1BX | 1 | | 100K|00:00:00.14 | 198 | 0 | | | |
|- 18 | STATISTICS COLLECTOR | | 1 | | 2 |00:00:00.01 | 207 | 0 | | | |
| 19 | BITMAP MERGE | | 1 | | 2 |00:00:00.01 | 207 | 0 | 1024K| 512K|47104 (0)|
| 20 | BITMAP KEY ITERATION | | 1 | | 2 |00:00:00.01 | 207 | 0 | | | |
| * 21 | TABLE ACCESS FULL | DIM2 | 1 | 1 | 1 |00:00:00.01 | 9 | 0 | | | |
| 22 | BITMAP CONVERSION FROM ROWIDS| | 1 | | 2 |00:00:00.01 | 198 | 0 | | | |
| * 23 | INDEX RANGE SCAN | DIM2BX | 1 | | 100K|00:00:00.12 | 198 | 0 | | | |
|- 24 | STATISTICS COLLECTOR | | 1 | | 5 |00:00:00.12 | 1984 | 3 | | | | >>>>====
|- 25 | BITMAP MERGE | | 1 | | 5 |00:00:00.12 | 1984 | 3 | 1024K| 512K| 1454K (0)| >>>>====
|- 26 | BITMAP KEY ITERATION | | 1 | | 50 |00:00:00.01 | 1984 | 3 | | | | >>>>====
|- * 27 | TABLE ACCESS FULL | DIM3 | 1 | 1 | 10 |00:00:00.01 | 9 | 0 | | | | >>>>====
|- 28 | BITMAP CONVERSION FROM ROWIDS| | 10 | | 50 |00:00:00.03 | 1975 | 3 | | | | >>>>====
|- * 29 | INDEX RANGE SCAN | DIM3BX | 10 | | 1000K|00:00:01.65 | 1975 | 3 | | | | >>>>====
| 30 | TABLE ACCESS BY USER ROWID | FACT | 10000 | 1 | 10000 |00:00:00.01 | 1044 | 0 | | | |
--------------------------------------------------------------------------------------------------------------------------------------------------------

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

1 - access("ITEM_3"="DIM3"."DIM3_ID" AND "ITEM_2"="DIM2"."DIM2_ID" AND "ITEM_1"="DIM1"."DIM1_ID")
4 - filter("DIM1"."DIM1_COD"='One')
6 - filter("DIM2"."DIM2_COD"='One')
8 - filter("DIM3"."DIM3_COD"='One')
15 - filter("DIM1"."DIM1_COD"='One')
17 - access("FACT"."DIM1_ID"="DIM1"."DIM1_ID")
21 - filter("DIM2"."DIM2_COD"='One')
23 - access("FACT"."DIM2_ID"="DIM2"."DIM2_ID")
27 - filter("DIM3"."DIM3_COD"='One')
29 - access("FACT"."DIM3_ID"="DIM3"."DIM3_ID")

Note
-----
- star transformation used for this statement
- this is an adaptive plan (rows marked '-' are inactive)

可以看到我标注的部分全部都被优化器忽略掉了,并没有走DIM3表的位图扫描、合并等操作,只是最后过滤的时候用到了DIM3进行了hash join。


12c Adaptive Plans
https://www.xbdba.com/2018/11/30/12c-adaptive-plans/
作者
xbdba
发布于
2018年11月30日
许可协议