oracle 表連接 - nested loop 嵌套循環連接


一. nested loop 原理

nested loop 連接(循環嵌套連接)指的是兩個表連接時, 通過兩層嵌套循環來進行依次的匹配, 最后得到返回結果集的表連接方法.

假如下面的 sql 語句中表 T1 和 T2 的連接方式是循環嵌套連接, T1 是驅動表
select *
from T1, T2
where T1.id = T2.id and T1.name = 'David';
那么將上述 sql 語句翻譯為偽碼應該如下所示:

1 for each row in (select * from T1 where name = 'David') loop
2 for (select * from T2 where T2.id = outer.id) loop
3 If match then pass the row on to the next step
4 If no match then discard the row
5 end loop
6 end loop

具體來說, 如果上述 sql 語句執行循環嵌套連接的話, 那么實際的執行過程應該如下所示:

(1) 首先 oracle 會根據一定的規則(根據統計信息的成本計算或者 hint 強制)決定哪個表是驅動表, 哪個表是被驅動表 (假設 T1 是驅動表)
(2) 查詢驅動表 "select * from T1 where name = 'David'" 然后得到驅動結果集 Q1
(3) 遍歷驅動結果集 Q1 以及被驅動表 T2, 從驅動結果集 Q1 中取出一條記錄, 接着遍歷 T2 並按照連接條件 T2.id = T1.id 去判斷 T2 中是否存在匹配的記錄,
如果能夠匹配則保留, 不能匹配則忽略此行, 然后再從 Q1 中取出下一條記錄, 接着遍歷 T2 進行匹配, 如此下去直到取完 Q1 中的所有記錄

具體來說, 如果上述 sql 語句執行循環嵌套連接的話, 那么實際的執行過程應該如下所示:
(1) 首先 oracle 會根據一定的規則(根據統計信息的成本計算或者 hint 強制)決定哪個表是驅動表, 哪個表是被驅動表 (假設 T1 是驅動表)
(2) 查詢驅動表 "select * from T1 where name = 'David'" 然后得到驅動結果集 Q1
(3) 遍歷驅動結果集 Q1 以及被驅動表 T2, 從驅動結果集 Q1 中取出一條記錄, 接着遍歷 T2 並按照連接條件 T2.id = T1.id 去判斷 T2 中是否存在匹配的記錄,
如果能夠匹配則保留, 不能匹配則忽略此行, 然后再從 Q1 中取出下一條記錄, 接着遍歷 T2 進行匹配, 如此下去直到取完 Q1 中的所有記錄

二. nested loop 特性

嵌套循環連接有以下特性:

(1) 通常 sql 語句中驅動表只訪問一次, 被驅動表訪問多次
(2) 不必等待處理完成所有行前可以先返回部分已經處理完成的數據
(3) 在限制條件以及連接條件列上建立索引, 能夠提高執行效率
(4) 支持所有類型的連接 (等值連接, 非等值連接, like 等)

構造試驗數據

SQL> CREATE TABLE t1 (
2 id NUMBER NOT NULL,
3 n NUMBER,
4 pad VARCHAR2(4000),
5 CONSTRAINT t1_pk PRIMARY KEY(id)
6 );
Table created.

SQL> CREATE TABLE t2 (
2 id NUMBER NOT NULL,
3 t1_id NUMBER NOT NULL,
4 n NUMBER,
5 pad VARCHAR2(4000),
6 CONSTRAINT t2_pk PRIMARY KEY(id),
7 CONSTRAINT t2_t1_fk FOREIGN KEY (t1_id) REFERENCES t1
8 ); 
Table created.

SQL> CREATE TABLE t3 (
2 id NUMBER NOT NULL,
3 t2_id NUMBER NOT NULL,
4 n NUMBER,
5 pad VARCHAR2(4000),
6 CONSTRAINT t3_pk PRIMARY KEY(id),
7 CONSTRAINT t3_t2_fk FOREIGN KEY (t2_id) REFERENCES t2
8 ); 
Table created.

SQL> CREATE TABLE t4 (
2 id NUMBER NOT NULL,
3 t3_id NUMBER NOT NULL,
4 n NUMBER,
5 pad VARCHAR2(4000),
6 CONSTRAINT t4_pk PRIMARY KEY(id),
7 CONSTRAINT t4_t3_fk FOREIGN KEY (t3_id) REFERENCES t3
8 ); 
Table created.

SQL> execute dbms_random.seed(0) 
PL/SQL procedure successfully completed.


SQL> INSERT INTO t1 SELECT rownum, rownum, dbms_random.string('a',50) FROM dual CONNECT BY level <= 10 ORDER BY dbms_random.random;
10 rows created.

SQL> INSERT INTO t2 SELECT 100+rownum, t1.id, 100+rownum, t1.pad FROM t1, t1 dummy ORDER BY dbms_random.random; 
100 rows created.

SQL> INSERT INTO t3 SELECT 1000+rownum, t2.id, 1000+rownum, t2.pad FROM t2, t1 dummy ORDER BY dbms_random.random; 
1000 rows created.

SQL> INSERT INTO t4 SELECT 10000+rownum, t3.id, 10000+rownum, t3.pad FROM t3, t1 dummy ORDER BY dbms_random.random; 
10000 rows created.

SQL> COMMIT; 
Commit complete.

 

使用 hint 讓 sql 語句通過 nested loop 連接, 並且指定 t3 為驅動表

 1 SQL> select /*+ leading(t3) use_nl(t4) */ * from t3, t4
 2 2 where t3.id = t4.t3_id and t3.n = 1100;
 3 
 4 10 rows selected.
 5 
 6 SQL> select * from table(dbms_xplan.display_cursor(null,null,'allstats last'));
 7 
 8 PLAN_TABLE_OUTPUT
 9 ---------------------------------------------------------------------------------------------
10 SQL_ID 89hnfwqakjghg, child number 0
11 -------------------------------------
12 select /*+ leading(t3) use_nl(t4) */ * from t3, t4 where t3.id = t4.t3_id and t3.n = 1100
13 
14 Plan hash value: 1907878852
15 
16 -------------------------------------------------------------------------------------
17 | Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
18 -------------------------------------------------------------------------------------
19 | 0 | SELECT STATEMENT | | 1 | | 10 |00:00:00.01 | 121 |
20 | 1 | NESTED LOOPS | | 1 | 10 | 10 |00:00:00.01 | 121 |
21 |* 2 | TABLE ACCESS FULL| T3 | 1 | 1 | 1 |00:00:00.01 | 16 |
22 |* 3 | TABLE ACCESS FULL| T4 | 1 | 10 | 10 |00:00:00.01 | 105 |
23 -------------------------------------------------------------------------------------
24 
25 Predicate Information (identified by operation id):
26 ---------------------------------------------------
27 
28 2 - filter("T3"."N"=1100)
29 3 - filter("T3"."ID"="T4"."T3_ID")

在執行計划中我們可以看到驅動表 T3 訪問一次, 因為驅動表上有謂詞條件 t3.n = 1100, 通過執行謂詞條件后驅動結果集的記錄數為 1, 所以 T4 也只訪問一次(starts 列)

使用 hint 讓 sql 語句通過 nested loop 連接, 並且指定 t4 為驅動表

 1 SQL> select /*+ leading(t4) use_nl(t3) full(t4) full(t3) */ * from t3, t4 where t3.id = t4.t3_id and t3.n = 1100;
 2 
 3 SQL> select * from table(dbms_xplan.display_cursor(null,null,'allstats last'));
 4 
 5 PLAN_TABLE_OUTPUT
 6 ----------------------------------------------------------------------------------------------------------
 7 SQL_ID 0yxm1muqwrfq2, child number 0
 8 -------------------------------------
 9 select /*+ leading(t4) use_nl(t3) full(t4) full(t3) */ * from t3, t4
10 where t3.id = t4.t3_id and t3.n = 1100
11 
12 Plan hash value: 3886808168
13 
14 -------------------------------------------------------------------------------------
15 | Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
16 -------------------------------------------------------------------------------------
17 | 0 | SELECT STATEMENT | | 1 | | 10 |00:00:00.25 | 150K|
18 | 1 | NESTED LOOPS | | 1 | 10 | 10 |00:00:00.25 | 150K|
19 | 2 | TABLE ACCESS FULL| T4 | 1 | 10000 | 10000 |00:00:00.01 | 105 |
20 |* 3 | TABLE ACCESS FULL| T3 | 10000 | 1 | 10 |00:00:00.21 | 150K|
21 -------------------------------------------------------------------------------------
22 
23 Predicate Information (identified by operation id):
24 ---------------------------------------------------
25 
26 3 - filter(("T3"."N"=1100 AND "T3"."ID"="T4"."T3_ID"))

在執行計划中我們可以看到驅動表 T4 訪問一次, 因為驅動表上 T4 結果集的記錄數為 10000, 所以 T4 訪問了 10000 次, buffers 和 A-time(實際執行時間) 都比較高.

三. nested loop 優化

在 nested loop 被驅動表上的連接列上 (T4 表的 t3_id 列) 建立索引

 1 SQL> CREATE INDEX t4_t3_id ON t4(t3_id);
 2 
 3 Index created.
 4 
 5 SQL> select /*+ leading(t3) use_nl(t4) */ * from t3, t4 where t3.id = t4.t3_id and t3.n = 1100;
 6 
 7 10 rows selected.
 8 
 9 SQL> select * from table(dbms_xplan.display_cursor(null,null,'allstats last'));
10 
11 PLAN_TABLE_OUTPUT
12 ------------------------------------------------------------------------------------------------------------------------------------
13 SQL_ID 89hnfwqakjghg, child number 0
14 -------------------------------------
15 select /*+ leading(t3) use_nl(t4) */ * from t3, t4 where t3.id = t4.t3_id and t3.n = 1100
16 
17 Plan hash value: 2039660043
18 
19 ------------------------------------------------------------------------------------------------------------
20 | Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | Reads |
21 ------------------------------------------------------------------------------------------------------------
22 | 0 | SELECT STATEMENT | | 1 | | 10 |00:00:00.01 | 29 | 1 |
23 | 1 | NESTED LOOPS | | 1 | | 10 |00:00:00.01 | 29 | 1 |
24 | 2 | NESTED LOOPS | | 1 | 10 | 10 |00:00:00.01 | 19 | 1 |
25 |* 3 | TABLE ACCESS FULL | T3 | 1 | 1 | 1 |00:00:00.01 | 16 | 0 |
26 |* 4 | INDEX RANGE SCAN | T4_T3_ID | 1 | 10 | 10 |00:00:00.01 | 3 | 1 |
27 | 5 | TABLE ACCESS BY INDEX ROWID| T4 | 10 | 10 | 10 |00:00:00.01 | 10 | 0 |
28 ------------------------------------------------------------------------------------------------------------
29 Predicate Information (identified by operation id):
30 ---------------------------------------------------
31 3 - filter("T3"."N"=1100)
32 4 - access("T3"."ID"="T4"."T3_ID")

 在執行計划中可以看到在被驅動表上的連接列上加上索引后, buffer 從 121 下降到了 29

在驅動表的謂詞條件列上 (T3 表的 n 列) 加上索引

SQL> create index t3_n on t3(n);

Index created.

SQL> select /*+ leading(t3) use_nl(t4) */ * from t3, t4 where t3.id = t4.t3_id and t3.n = 1100;

10 rows selected.

SQL> select * from table(dbms_xplan.display_cursor(null,null,'allstats last'));

PLAN_TABLE_OUTPUT
-------------------------------------------------------------------------------------------------------------------------------------
SQL_ID 89hnfwqakjghg, child number 0
-------------------------------------
select /*+ leading(t3) use_nl(t4) */ * from t3, t4 where t3.id = t4.t3_id and t3.n = 1100

Plan hash value: 2304842513

-------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | Reads |
-------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 10 |00:00:00.01 | 17 | 1 |
| 1 | NESTED LOOPS | | 1 | | 10 |00:00:00.01 | 17 | 1 |
| 2 | NESTED LOOPS | | 1 | 10 | 10 |00:00:00.01 | 7 | 1 |
| 3 | TABLE ACCESS BY INDEX ROWID| T3 | 1 | 1 | 1 |00:00:00.01 | 4 | 1 |
|* 4 | INDEX RANGE SCAN | T3_N | 1 | 1 | 1 |00:00:00.01 | 3 | 1 |
|* 5 | INDEX RANGE SCAN | T4_T3_ID | 1 | 10 | 10 |00:00:00.01 | 3 | 0 |
| 6 | TABLE ACCESS BY INDEX ROWID | T4 | 10 | 10 | 10 |00:00:00.01 | 10 | 0 |
-------------------------------------------------------------------------------------------------------------

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

4 - access("T3"."N"=1100)
5 - access("T3"."ID"="T4"."T3_ID")

  在執行計划中可以看到在驅動表上的謂詞條件列上加上索引后, buffer 從 29 繼續下降到了 17

四. 小結

由此可見, 在 sql 調優時如果遇到表的連接方式是 nested loop:

首先,要確保結果集小的表為驅動表,結果集多的表為被驅動表。這不意味着記錄多的表不能作為驅動表, 只要通過謂詞條件過濾后得到的結果集比較小,也可以作為驅動表。

其次,在驅動表的謂詞條件列以及被驅動表的連接列上加上索引,能夠顯著的提高執行性能。

最后,如果要查詢的列都在索引中,避免回表查詢列信息時,又將進一步提高執行性能。

 

https://blog.csdn.net/dataminer_2007/article/details/41826915

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM