创建测试表

SQL> create table t as select rownum id from dba_objects;


Table created.


SQL> create index idx_t on t(id);


Index created.



SQL> exec dbms_stats.gather_table_stats('sys','t',cascade=>true);


PL/SQL procedure successfully completed.



SQL> create table t1 as select id,'T1' name from t where id<1000;


Table created.



SQL> create index idx_t1 on t1(id);


Index created.



SQL> exec dbms_stats.gather_table_stats('sys','t1',cascade=>true);


PL/SQL procedure successfully completed.


上面的表中t表有7万多行,t1表1000行数据


SQL> set autotrace traceonly;

SQL> select * from t,t1 where t1.id<100 and t.id=t1.id;


99 rows selected.



Execution Plan

----------------------------------------------------------

Plan hash value: 3627535484


----------------------------------------------------------------------------

| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |

----------------------------------------------------------------------------

| 0 | SELECT STATEMENT | | 98 | 1176 | 5 (20)| 00:00:01 |

|* 1 | HASH JOIN | | 98 | 1176 | 5 (20)| 00:00:01 |

|* 2 | INDEX RANGE SCAN | IDX_T | 99 | 495 | 2 (0)| 00:00:01 |

|* 3 | TABLE ACCESS FULL| T1 | 99 | 693 | 2 (0)| 00:00:01 |

----------------------------------------------------------------------------


Predicate Information (identified by operation id):

---------------------------------------------------


1 - access("T"."ID"="T1"."ID")

2 - access("T"."ID"<100)

3 - filter("T1"."ID"<100)



Statistics

----------------------------------------------------------

0 recursive calls

0 db block gets

13 consistent gets

0 physical reads

0 redo size

2881 bytes sent via SQL*Net to client

590 bytes received via SQL*Net from client

8 SQL*Net roundtrips to/from client

0 sorts (memory)

0 sorts (disk)

99 rows processed



从上面看出CBO默认根据统计信息计算使用Hash join方式,逻辑读13

SQL> select /*+ use_nl(t,t1) */ * from t,t1 where t1.id<100 and t.id=t1.id;


99 rows selected.



Execution Plan

----------------------------------------------------------

Plan hash value: 967599162


----------------------------------------------------------------------------

| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |

----------------------------------------------------------------------------

| 0 | SELECT STATEMENT | | 98 | 1176 | 58 (2)| 00:00:01 |

| 1 | NESTED LOOPS | | 98 | 1176 | 58 (2)| 00:00:01 |

|* 2 | INDEX RANGE SCAN | IDX_T | 99 | 495 | 2 (0)| 00:00:01 |

|* 3 | TABLE ACCESS FULL| T1 | 1 | 7 | 1 (0)| 00:00:01 |

----------------------------------------------------------------------------


Predicate Information (identified by operation id):

---------------------------------------------------


2 - access("T"."ID"<100)

3 - filter("T1"."ID"<100 AND "T"."ID"="T1"."ID")



Statistics

----------------------------------------------------------

1 recursive calls

0 db block gets

412 consistent gets

0 physical reads

0 redo size

2881 bytes sent via SQL*Net to client

590 bytes received via SQL*Net from client

8 SQL*Net roundtrips to/from client

0 sorts (memory)

0 sorts (disk)

99 rows processed

当使用hint强制使用nested join方式逻辑读为412,远远超过了hash join


SQL> select /*+ use_merge(t,t1) */ * from t,t1 where t1.id<100 and t.id=t1.id;


99 rows selected.



Execution Plan

----------------------------------------------------------

Plan hash value: 3760646495


-----------------------------------------------------------------------------

| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |

-----------------------------------------------------------------------------

| 0 | SELECT STATEMENT | | 98 | 1176 | 5 (20)| 00:00:01 |

| 1 | MERGE JOIN | | 98 | 1176 | 5 (20)| 00:00:01 |

|* 2 | INDEX RANGE SCAN | IDX_T | 99 | 495 | 2 (0)| 00:00:01 |

|* 3 | SORT JOIN | | 99 | 693 | 3 (34)| 00:00:01 |

|* 4 | TABLE ACCESS FULL| T1 | 99 | 693 | 2 (0)| 00:00:01 |

-----------------------------------------------------------------------------


Predicate Information (identified by operation id):

---------------------------------------------------


2 - access("T"."ID"<100)

3 - access("T"."ID"="T1"."ID")

filter("T"."ID"="T1"."ID")

4 - filter("T1"."ID"<100)



Statistics

----------------------------------------------------------

1 recursive calls

0 db block gets

13 consistent gets

0 physical reads

0 redo size

2881 bytes sent via SQL*Net to client

590 bytes received via SQL*Net from client

8 SQL*Net roundtrips to/from client

1 sorts (memory)

0 sorts (disk)

99 rows processed


当使用hint强制使用Merge join方式逻辑读与hash join一致,但是多了一次内存排序,因此最优的还是hash join



继续下面实验:

也可以通过hint改变基数的方式走nested loop join

SQL> select /*+ cardinality(t1 1) */ * from t,t1 where t1.id<100 and t.id=t1.id;



----------------------------------------------------------------------------

| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |

----------------------------------------------------------------------------

| 0 | SELECT STATEMENT | | 1 | 12 | 3 (0)| 00:00:01 |

| 1 | NESTED LOOPS | | 1 | 12 | 3 (0)| 00:00:01 |

|* 2 | TABLE ACCESS FULL| T1 | 1 | 7 | 2 (0)| 00:00:01 |

|* 3 | INDEX RANGE SCAN | IDX_T | 1 | 5 | 1 (0)| 00:00:01 |

----------------------------------------------------------------------------


从这里就可以看出在连接一个小表的时候CBO会选择nested loop join


SQL> delete t1 where id>10;


989 rows deleted.


SQL> commit;


Commit complete.


SQL> select count(*) from t1;


COUNT(*)

----------

10


SQL> select * from t,t1 where t.id=t1.id;


10 rows selected.



Execution Plan

----------------------------------------------------------

Plan hash value: 1557655968


----------------------------------------------------------------------------

| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |

----------------------------------------------------------------------------

| 0 | SELECT STATEMENT | | 10 | 110 | 12 (0)| 00:00:01 |

| 1 | NESTED LOOPS | | 10 | 110 | 12 (0)| 00:00:01 |

| 2 | TABLE ACCESS FULL| T1 | 10 | 60 | 2 (0)| 00:00:01 |

|* 3 | INDEX RANGE SCAN | IDX_T | 1 | 5 | 1 (0)| 00:00:01 |

----------------------------------------------------------------------------


Predicate Information (identified by operation id):

---------------------------------------------------


3 - access("T"."ID"="T1"."ID")



Statistics

----------------------------------------------------------

0 recursive calls

0 db block gets

14 consistent gets

0 physical reads

0 redo size

804 bytes sent via SQL*Net to client

524 bytes received via SQL*Net from client

2 SQL*Net roundtrips to/from client

0 sorts (memory)

0 sorts (disk)

10 rows processed


从上面看出当连接一张小表的时候,CBO计算出使用nested loops join方式

SQL> select /*+ use_hash(t,t1) */ * from t,t1 where t.id=t1.id;


10 rows selected.



Execution Plan

----------------------------------------------------------

Plan hash value: 1444793974


---------------------------------------------------------------------------

| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |

---------------------------------------------------------------------------

| 0 | SELECT STATEMENT | | 10 | 110 | 35 (3)| 00:00:01 |

|* 1 | HASH JOIN | | 10 | 110 | 35 (3)| 00:00:01 |

| 2 | TABLE ACCESS FULL| T1 | 10 | 60 | 2 (0)| 00:00:01 |

| 3 | TABLE ACCESS FULL| T | 73961 | 361K| 32 (0)| 00:00:01 |

---------------------------------------------------------------------------


Predicate Information (identified by operation id):

---------------------------------------------------


1 - access("T"."ID"="T1"."ID")



Statistics

----------------------------------------------------------

1 recursive calls

0 db block gets

122 consistent gets

0 physical reads

0 redo size

804 bytes sent via SQL*Net to client

524 bytes received via SQL*Net from client

2 SQL*Net roundtrips to/from client

0 sorts (memory)

0 sorts (disk)

10 rows processed



当我们强制使用hash join 可以看到逻辑读增加到了122,相差100多


SQL> select /*+ use_merge(t,t1) */ * from t,t1 where t.id=t1.id;


10 rows selected.



Execution Plan

----------------------------------------------------------

Plan hash value: 4143939902


-----------------------------------------------------------------------------

| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |

-----------------------------------------------------------------------------

| 0 | SELECT STATEMENT | | 10 | 110 | 169 (2)| 00:00:03 |

| 1 | MERGE JOIN | | 10 | 110 | 169 (2)| 00:00:03 |

| 2 | INDEX FULL SCAN | IDX_T | 73961 | 361K| 166 (1)| 00:00:02 |

|* 3 | SORT JOIN | | 10 | 60 | 3 (34)| 00:00:01 |

| 4 | TABLE ACCESS FULL| T1 | 10 | 60 | 2 (0)| 00:00:01 |

-----------------------------------------------------------------------------


Predicate Information (identified by operation id):

---------------------------------------------------


3 - access("T"."ID"="T1"."ID")

filter("T"."ID"="T1"."ID")



Statistics

----------------------------------------------------------

1 recursive calls

0 db block gets

7 consistent gets

0 physical reads

0 redo size

804 bytes sent via SQL*Net to client

524 bytes received via SQL*Net from client

2 SQL*Net roundtrips to/from client

1 sorts (memory)

0 sorts (disk)

10 rows processed


使用merge join 逻辑读最低,但是多了一次内存排序,CBO最终还是选择了nested loops join


SQL> drop index idx_t ;


Index dropped.


SQL> exec dbms_stats.gather_table_stats('sys','t',cascade=>true);


PL/SQL procedure successfully completed.


SQL> select * from t,t1 where t.id=t1.id;


20 rows selected.



Execution Plan

----------------------------------------------------------

Plan hash value: 1444793974


---------------------------------------------------------------------------

| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |

---------------------------------------------------------------------------

| 0 | SELECT STATEMENT | | 20 | 220 | 67 (3)| 00:00:01 |

|* 1 | HASH JOIN | | 20 | 220 | 67 (3)| 00:00:01 |

| 2 | TABLE ACCESS FULL| T1 | 10 | 60 | 2 (0)| 00:00:01 |

| 3 | TABLE ACCESS FULL| T | 147K| 722K| 64 (2)| 00:00:01 |

---------------------------------------------------------------------------


Predicate Information (identified by operation id):

---------------------------------------------------


1 - access("T"."ID"="T1"."ID")



Statistics

----------------------------------------------------------

135 recursive calls

0 db block gets

250 consistent gets

0 physical reads

0 redo size

1097 bytes sent via SQL*Net to client

535 bytes received via SQL*Net from client

3 SQL*Net roundtrips to/from client

3 sorts (memory)

0 sorts (disk)

20 rows processed


如上当删除索引后cbo会选择hash join


总结:当多表连接的时候,如果是大表之间hash join速度最快,如果其中一个表相对来说比较小,并且关联字段上有索引的时候nested loops 速度比较快,上面实验也证实了如果CBO判断基数不准确会导致错误的执行计划。