最近看執行計劃時,老是看到nested loops/hash join這些詞,於是做了個實驗,貼出來如果錯誤請指出。 ps:順便也談了下CBO 1.創建表t1,t2 都只有很少的幾行數據,對錶t1加個索引 SQL> create table t1(id number, name varchar2(10)); Table created. SQL> create table t2(id number, name varchar2(10)); Table created. SQL> insert into t1 values(1, 'test'); 1 row created. SQL> insert into t1 values(2, 'test'); 1 row created. SQL> insert into t2 values(2, 'test'); 1 row created. SQL> commit; Commit complete. SQL> create index idx_t1 on t1(id); Index created. 打開執行計劃跟蹤: SQL> set autotrace trace exp; SQL> set linesize 180 SQL> select * from t1, t2 where t1.id=t2.id; Execution Plan ---------------------------------------------------------- Plan hash value: 580336636 -------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time| -------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 40 | 3 (0)| 00:00:01 | | 1 | TABLE ACCESS BY INDEX ROWID| T1 | 1 | 0 |1 (0)| 00:00:01 | | 2 | NESTED LOOPS | | 1 | 40 | 3 (0)| 00:00:01 | | 3 | TABLE ACCESS FULL | T2 | 1 | 20 | 2 (0)|00:00:01| |* 4 | INDEX RANGE SCAN | IDX_T1 | 1 | | 0 (0)| 00:00:01 | -------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 4 - access("T1"."ID"="T2"."ID") Note ----- - dynamic sampling used for this statement(請注意,只有第一次纔會自動分析,以後都要自己來手動分析) 因爲表t1在連接字段id上加了索引所以對於t1表的掃描是走的索引,而t2是全表掃描。兩個表的連接方式是nested loops(等下會說明原因) (2)接着對t2的id上建立一個索引 接着再執行對上面的SQL語句查看執行計劃,依然一樣。第一感覺是覺得不對,對t2表應該也走索引的,但是仔細一想,因爲這個特殊情況t2表裏面只有全爲id=2的字段,如果還走索引代價反而更高。而在10g中默認的是採用的CBO優化器,所以出現這種情況就很正常了。 (3)接着對錶t2插入一些數據。 Declare Begin For I int 2..1000 loop Insert into t2 values(3,’test’);--這裏其實應該綁定變量的 End loop; End; 然後insert into t1 values(3, ‘test’); 再執行selec * from t1, t2 where t1.id=t2.id 執行計劃依然和上面一樣: SQL> select * from t2, t1 where t2.id=t1.id; Execution Plan ---------------------------------------------------------- Plan hash value: 580336636 -------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time| -------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 999 | 15984 | 4 (25)| 00:00:01 | | 1 | TABLE ACCESS BY INDEX ROWID| T1 | 1 | 8 | 1 (0)| 00:00:01 | | 2 | NESTED LOOPS | | 999 | 15984 | 4 (25)| 00:00:01 | | 3 | TABLE ACCESS FULL | T2 | 999 | 7992 | 2 (0)| 00:00:01 | |* 4 | INDEX RANGE SCAN | IDX_T1 | 1 | | 0 (0)|00:00:01| -------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 4 - access("T2"."ID"="T1"."ID") 這又是爲什麼呢?同上面一樣,雖然在兩個表在連接時在id=2這行記錄上使用,對t2使用inde range scan(使用索引)效率會很高,但是對於id=3這行就不同了,因爲在t2中存在幾乎所有的行都是id=3,CBO做這樣的決定是明智的。 接着我將SQL語句改爲 SQL> select * from t2, t1 where t2.id=t1.id and t2.id=2;紅色部分限制了等下連接時,t2的cardinality值(篩選出來行的佔表總行數比)很小,那麼使用索引的價值就出來了。於是看到執行計劃: Execution Plan ---------------------------------------------------------- Plan hash value: 4102592341 ---------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ---------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 16 | 3 (0)| 00:00:01 | | 1 | TABLE ACCESS BY INDEX ROWID | T1 | 1 | 8 | 1 (0)| 00 :00:01 | | 2 | NESTED LOOPS | | 1 | 16 | 3 (0)| 00:00:01 | | 3 | TABLE ACCESS BY INDEX ROWID| T2 | 1 | 8 | 2 (0)| 00:00:01 | |* 4 | INDEX RANGE SCAN | IDX_T2 | 1 | | 1 (0)| 00 :00:01 | |* 5 | INDEX RANGE SCAN | IDX_T1 | 1 | | 0 (0)| 00 :00:01 | -------------------------------------------------------------------------------- -------- Predicate Information (identified by operation id): --------------------------------------------------- 4 - access("T2"."ID"=2) 5 - access("T1"."ID"=2) (4)接着對t1也插入大量數據。 Declare Begin For I in 2..1000 loop Insert into t2 values(3,’test’);--這裏其實應該綁定變量的 End loop; End; 當然必須先手動執行分析,因爲我們已經對錶插入了數據。 SQL> exec dbms_stats.gather_table_stats(user,'t1',cascade=>true); PL/SQL procedure successfully completed. SQL> exec dbms_stats.gather_table_stats(user,'t2',cascade=>true); PL/SQL procedure successfully completed. SQL> select * from t1,t2 where t1.id=t2.id; Execution Plan ---------------------------------------------------------- Plan hash value: 2959412835 --------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | --------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 998K| 15M| 22 (82)| 00:00:01 | |* 1 | HASH JOIN | | 998K| 15M| 22 (82)| 00:00:01 | | 2 | TABLE ACCESS FULL| T2 | 999 | 7992 | 2 (0)| 00:00:01 | | 3 | TABLE ACCESS FULL| T1 | 1002 | 8016 | 2 (0)| 00:00:01 | --------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 1 - access("T1"."ID"="T2"."ID") 可以看到,此時兩個表都是全表掃描了,CBO的功力展現了因爲兩個表中用索引篩選效果不明顯,在連接字段幾乎都是一樣。同時也應該注意,兩個表之間的連接方式是hash join了。 (5)那麼什麼是hash join/nested loops,什麼情況下選擇hash join,什麼情況選擇 nested loops? Hash join:在將內表中的符合條件的行全部篩選出來,然後對連接字段進行hash,得到一個hash系列集,放置內存區域,然後再外表的行的那個連接字段進行hash,如果得到的hash值在之前的那個hash集合裏面存在的話,那麼這行就是符合條件的。更詳細的解答請看這篇文章:http://www.eygle.com/rss/20101128.html Nested loops:就是對於內表符合條件的結果集較少的情況使用,然後在內表每次取出一行,再掃描外表,看是否有符合的。循環下去,知道內表符合條件的每行都遍歷了一遍。 那麼爲什麼情況使用它們呢?因爲對於hash join來說,它的時間複雜度大致相當於兩個for循環(並非嵌套)O(n+m)(n,m表示內外表掃描的時間複雜度,也可以簡單的理解爲是內外表符合條件的行數,雖然有些不恰當),爲什麼這麼說呢?因爲hash join是先把內表的結果集全部算出來,完了之後再對外表做一個全表掃描。所以說是O(n+m)。nested loops,對於內表的符合條件的每行,都會在外表去掃描一下,看是外表否有符合條件的行。這個就相當於兩個for循環嵌套了時間複雜度O(n*m)。所以在對於m或是n中有一個很小的情況下(內表的符合條件很少的情況下),那麼O(n*m)的時間比O(m+n)的時間可能更少。但是對於m和n都很大的情況下,當然就會選擇O(m+n)了。 這就是我自己的一點點心得,如果有什麼理解錯誤的地方請指出。 順便問個問題: 因爲有資料說,只有SQL第一次執行時才做動態採樣分析,那麼以後如果表裏面數據變化了都要我們手動做嗎?在應該程序中,不會時間久了就會效率越來越低啊? 難道是過那麼一段時間有DBA來手動做動態採樣分析?或者寫個觸發器,隔那麼久做一次? |
nested loops/hash join
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.