一種特殊場景下的HASH JOIN的優化爲NEST LOOP.

應用場景:web

有以下的SQL:dom

select t.*, t1.owner
from t, t1
where t.id=t1.id;ide

表t ,t1的數據量比較大,好比200W行.可是兩張表能關聯的行數卻不多,好比不到50條. T1表的行比較寬,且在id列上有單列索引.oop

這裏限制t1的索引爲單列索引是爲了讓訪問t1表數據的時候要經過rowid回表.由於在實際應用中,咱們一般會須要t1表的幾個列,可是不可能對它們全都索引.優化

此時能夠優化爲下面的SQL.spa

select /*+ use_nl(t1, t) ordered */  t.*, t1.ownercode

from t, t1
where t.id=t1.id and t1.id is not null;
改寫原理:orm

1. 當咱們使用NEST LOOP的時候,t1表可使用id列上的index走出INDEX RANGE SCAN,此時若是輸入值不在索引範圍內時,是可以很快返回的,理論上就無須訪問索引的leaf塊。只需訪問root和部分branch便可.而這裏咱們可以真正匹配的數據不多,因此大部分時候都沒有到達leaf.blog

不過這種場景實際應用中並不常見,因此在CBO看來, cost= rows of t * 3(假設index深度爲3), 這個cost遠大於hash join的cost。若是不加hint,CBO是不會選這個執行計劃的.索引

  1.   select  /*+ use_nl(p, l) ordered gather_plan_statistics */                                                                      
  2. l.l_suppkey    from partsupp p ,lineitem l    where                                                                               
  3. l.l_suppkey=p.ps_suppkey+10000010    and p.ps_suppkey>0                                                                           
  4.                                                                                                                                   
  5. Plan hash value: 3264550197                                                                                                       
  6.                                                                                                                                   
  7. -------------------------------------------------------------------------------------------------------------------------------   
  8. | Id  | Operation             | Name        | Starts | E-Rows |E-Bytes| Cost (%CPU)| E-Time   | A-Rows |   A-Time   | Buffers |   
  9. -------------------------------------------------------------------------------------------------------------------------------   
  10. |   0 | SELECT STATEMENT      |             |      1 |        |       |  2405K(100)|          |      0 |00:00:00.91 |    1698 |   
  11. |   1 |  NESTED LOOPS         |             |      1 |    480M|  3662M|  2405K  (1)| 08:01:03 |      0 |00:00:00.91 |    1698 |   
  12. |*  2 |   INDEX FAST FULL SCAN| I_PARTSUPP1 |      1 |    800K|  3125K|   308   (2)| 00:00:04 |    800K|00:00:00.18 |    1685 |   
  13. |*  3 |   INDEX RANGE SCAN    | I_LINEITEM1 |    800K|    600 |  2400 |     3   (0)| 00:00:01 |      0 |00:00:00.62 |      13 |   
  14. -------------------------------------------------------------------------------------------------------------------------------   

 好比下圖,要訪問10051的數據,經過root節點就知道沒這個key了,根本不需完成一次index range scan,而在loop join的下一次循環若是是要10052,直接在cache裏快速匹配便可,這個是我猜想的,在CBO執行過程當中這個應該很容易實現,至關於作預先處理,這樣的預處理再結合storage index就造成了smart scan。

 2. HASH JOIN時須要真正拿到索引中t1.id列所有數據後再作匹配,所以此時會走IFS或IFFS,直接遍歷索引的全部葉子節點,而後創建hash表.

當索引裏記錄比較多同時可以匹配的記錄有不多是,這種方法不如上面的NEST LOOP.

 

實驗準備:

表的建立和數據的準備,須要的時間比較久,得耐心等待.

--表T

create table t (
id number,
col1 varchar2(30),
col2 varchar2(30),
col3 varchar2(30),
col4 varchar2(30),
col5 varchar2(30),
col6 varchar2(30),
col7 varchar2(30),
col8 varchar2(30),
col9 varchar2(30),
col10 varchar2(30)
);

--隨機生成約200W行數據

insert into t select
rownum,
dbms_random.string('u',30),
dbms_random.string('u',30),
dbms_random.string('u',30),
dbms_random.string('u',30),
dbms_random.string('u',30),
dbms_random.string('u',30),
dbms_random.string('u',30),
dbms_random.string('u',30),
dbms_random.string('u',30),
dbms_random.string('u',30) 
from dual connect by rownum<2000000;

--T1變的行寬比較大,此時full table scan 讀的塊較多.

create table t1 (
id number,
id2 number,
id3 varchar2(30),
id4 varchar2(30),
id5 varchar2(30),
id6 varchar2(30),
id7 varchar2(30),
id8 varchar2(30),
id9 varchar2(30),
id10 varchar2(30),
id11 varchar2(30),
id12 varchar2(30),
id13 varchar2(30),
id14 varchar2(30),
id15 varchar2(30),
id16 varchar2(30),
id17 varchar2(30),
id18 varchar2(30),
id19 varchar2(30),
id20 varchar2(30),
id21 varchar2(30), 
owner varchar2(30));

--T1表上建立索引

create index t1_idx_id on t1(id);

--隨機生成約200W行數據

insert into t1 select
rownum+2000000,
round(dbms_random.value(1,100000000)),
dbms_random.string('u',30),
dbms_random.string('u',30),
dbms_random.string('u',30),
dbms_random.string('u',30),
dbms_random.string('u',30),
dbms_random.string('u',30),
dbms_random.string('u',30),
dbms_random.string('u',30),
dbms_random.string('u',30),
dbms_random.string('u',30),
dbms_random.string('u',30),
dbms_random.string('u',30),
dbms_random.string('u',30),
dbms_random.string('u',30),
dbms_random.string('u',30),
dbms_random.string('u',30),
dbms_random.string('u',30),
dbms_random.string('u',30),
dbms_random.string('u',30),
dbms_random.string('u',30)
from dual connect by rownum<2000000;

--收集表的統計信息

exec dbms_stats.gather_table_stats(ownname=>'SYS',tabname=>'T',estimate_percent=>100,method_opt=>'for all columns size auto',cascade=>TRUE)
exec dbms_stats.gather_table_stats(ownname=>'SYS',tabname=>'T1',estimate_percent=>100,method_opt=>'for all columns size auto',cascade=>TRUE)

--讓表T, T1 有少數的關聯行,這裏是製造10條關聯記錄.

update t1 set id = (select round(dbms_random.value(1,1999999)) from dual)
where id in (select round(dbms_random.value(2000001,4000000)) from dual connect by rownum<10) and id is not null;

實驗開始:

SQL> select t.*, t1.owner
  2  from t, t1
  3  where t.id=t1.id and t1.id is not null;

已選擇10行。

已用時間:  00: 00: 18.54

執行計劃
----------------------------------------------------------
Plan hash value: 1444793974

-----------------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |     1 |   352 |       |   109K  (1)| 00:21:56 |
|*  1 |  HASH JOIN         |      |     1 |   352 |    93M|   109K  (1)| 00:21:56 |
|*  2 |   TABLE ACCESS FULL| T1   |  1999K|    70M|       | 49367   (1)| 00:09:53 |
|   3 |   TABLE ACCESS FULL| T    |  1999K|   600M|       | 24691   (1)| 00:04:57 |
-----------------------------------------------------------------------------------

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

   1 - access("T"."ID"="T1"."ID")
   2 - filter("T1"."ID" IS NOT NULL)


統計信息
----------------------------------------------------------
         97  recursive calls
          0  db block gets
     454560  consistent gets
     283928  physical reads
          0  redo size
       2086  bytes sent via SQL*Net to client
        420  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
         10  rows processed

 全表掃描的T1時,18S。

 

SQL> select /*+index(t1 T1_IDX_ID)*/t.*, t1.owner
  2  from t, t1
  3  where t.id=t1.id and t1.id is not null;

已選擇10行。

已用時間:  00: 01: 52.29

執行計劃
----------------------------------------------------------
Plan hash value: 2604990230

--------------------------------------------------------------------------------------------------
| Id  | Operation                    | Name      | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |           |     1 |   352 |       |   246K  (1)| 00:49:19 |
|*  1 |  HASH JOIN                   |           |     1 |   352 |    93M|   246K  (1)| 00:49:19 |
|   2 |   TABLE ACCESS BY INDEX ROWID| T1        |  1999K|    70M|       |   186K  (1)| 00:37:15 |
|*  3 |    INDEX FULL SCAN           | T1_IDX_ID |  1999K|       |       |  4279   (1)| 00:00:52 |
|   4 |   TABLE ACCESS FULL          | T         |  1999K|   600M|       | 24691   (1)| 00:04:57 |
--------------------------------------------------------------------------------------------------

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

   1 - access("T"."ID"="T1"."ID")
   3 - filter("T1"."ID" IS NOT NULL)


統計信息
----------------------------------------------------------
         98  recursive calls
          0  db block gets
     458819  consistent gets
     271761  physical reads
   13092036  redo size
       2086  bytes sent via SQL*Net to client
        420  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
         10  rows processed

SQL> select /*+ use_nl(t1, t) ordered */t.*, t1.owner
  2  from t, t1
  3  where t.id=t1.id and t1.id is not null;

已選擇10行。

已用時間:  00: 00: 04.81

執行計劃
----------------------------------------------------------
Plan hash value: 3173170385

------------------------------------------------------------------------------------------
| Id  | Operation                    | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |           |     1 |   352 |  4026K  (1)| 13:25:15 |
|   1 |  NESTED LOOPS                |           |       |       |            |          |
|   2 |   NESTED LOOPS               |           |     1 |   352 |  4026K  (1)| 13:25:15 |
|   3 |    TABLE ACCESS FULL         | T         |  1999K|   600M| 24691   (1)| 00:04:57 |
|*  4 |    INDEX RANGE SCAN          | T1_IDX_ID |     1 |       |     2   (0)| 00:00:01 |
|   5 |   TABLE ACCESS BY INDEX ROWID| T1        |     1 |    37 |     2   (0)| 00:00:01 |
------------------------------------------------------------------------------------------

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

   4 - access("T"."ID"="T1"."ID")
       filter("T1"."ID" IS NOT NULL)


統計信息
----------------------------------------------------------
          0  recursive calls
          0  db block gets
      90946  consistent gets
      90914  physical reads
          0  redo size
       2086  bytes sent via SQL*Net to client
        420  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
         10  rows processed
  

select /*+ use_nl(t1, t) ordered  gather_plan_statistics*/t.*, t1.owner
from t, t1 where t.id=t1.id and t1.id is not null

Plan hash value: 3173170385

---------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                    | Name      | Starts | E-Rows |E-Bytes| Cost (%CPU)| E-Time   | A-Rows |   A-Time   | Buffers | Reads  |
---------------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |           |      4 |        |       |  4026K(100)|          |  40 |00:01:04.01 |     363K|       363K|
|   1 |  NESTED LOOPS                |           |      4 |        |       |            |          |  40 |00:01:04.01 |     363K|       363K|
|   2 |   NESTED LOOPS               |           |      4 |      1 |   352 |  4026K  (1)| 13:25:15 |  40 |00:01:04.01 |     363K|       363K|
|   3 |    TABLE ACCESS FULL         | T         |      4 |   1999K|   600M| 24691   (1)| 00:04:57 |   7999K|00:00:33.47 |     363K|    363K|
|*  4 |    INDEX RANGE SCAN          | T1_IDX_ID |   7999K|      1 |       |     2   (0)| 00:00:01 |  40 |00:00:51.27 | 60 |      0 |
|   5 |   TABLE ACCESS BY INDEX ROWID| T1        |     40 |      1 |    37 |     2   (0)| 00:00:01 |  40 |00:00:00.01 | 40 |      0 |
---------------------------------------------------------------------------------------------------------------------------------------------

相關文章
相關標籤/搜索