# 요약
  Nested Loop Join
■ Sort Merge Join

 

 


 

 

# 예시 1
SYS@ora11g> drop table hr.emp purge;
SYS@ora11g> drop table hr.dept purge;
SYS@ora11g> drop table hr.loc purge;

SYS@ora11g> create table hr.emp nologging as select * from hr.employees;

Table created.

SYS@ora11g> create table hr.dept nologging as select * from hr.departments;

Table created.

SYS@ora11g> create table hr.loc nologging as select * from hr.locations;

Table created.

SYS@ora11g> exec dbms_stats.gather_table_stats('hr','emp')

PL/SQL procedure successfully completed.

SYS@ora11g> exec dbms_stats.gather_table_stats('hr','dept')

PL/SQL procedure successfully completed.

SYS@ora11g> exec dbms_stats.gather_table_stats('hr','loc')

PL/SQL procedure successfully completed.

-- 테이블 정보
SYS@ora11g> select table_name, num_rows, blocks, avg_row_len, last_analyzed 
            from user_tables 
            where table_name in ('EMP','DEPT','LOC');

TABLE_NAME                       NUM_ROWS     BLOCKS AVG_ROW_LEN LAST_ANAL
------------------------------ ---------- ---------- ----------- ---------
DEPT                                   27          4          21 19-FEB-24
EMP                                   107          5          69 19-FEB-24
LOC                                    23          4          49 19-FEB-24

-- 인덱스 정보
SYS@ora11g> select a.table_name, a.constraint_name, b.column_name, a.constraint_type, a.search_condition, a.r_constraint_name, a.index_name
            from user_constraints a, user_cons_columns b
            where a.constraint_name = b.constraint_name
            and a.table_name in ('EMP','DEPT','LOC');
            
TABLE_NAME CONSTRAINT_NAME COLUMN_NAME     C SEARCH_CONDITION              R_CONSTRAINT_NAME INDEX_NAME
---------- --------------- --------------- - ----------------------------- ----------------- ----------
DEPT       SYS_C0011668    DEPARTMENT_NAME C "DEPARTMENT_NAME" IS NOT NULL
EMP        SYS_C0011664    LAST_NAME       C "LAST_NAME" IS NOT NULL
EMP        SYS_C0011665    EMAIL           C "EMAIL" IS NOT NULL
EMP        SYS_C0011666    HIRE_DATE       C "HIRE_DATE" IS NOT NULL
EMP        SYS_C0011667    JOB_ID          C "JOB_ID" IS NOT NULL
LOC        SYS_C0011669    CITY            C "CITY" IS NOT NULL


HR@ora11g> select /*+ gather_plan_statistics */ e.last_name, e.job_id, d.department_name, l.city
           from emp e, dept d, loc l
           where e.department_id = d.department_id
           and d.location_id = l.location_id
           and e.employee_id = 100;

LAST_NAME                 JOB_ID     DEPARTMENT_NAME                CITY
------------------------- ---------- ------------------------------ ------------------------------
King                      AD_PRES    Executive                      Seattle

1 rows selected.

-- 실행계획
HR@ora11g> select * from table(dbms_xplan.display_cursor(null,null,'allstats last'));

-----------------------------------------------------------------------------------------------------------------
| Id  | Operation             | Name | Starts | E-Rows | A-Rows |   A-Time   | Buffers | OMem | 1Mem | Used-Mem |
-----------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |      |      1 |        |      1 |00:00:00.01 |       8 |      |      |          |
|*  1 |  HASH JOIN            |      |      1 |      1 |      1 |00:00:00.01 |       8 |  922K|  922K| 1180K (0)|
|   2 |   MERGE JOIN CARTESIAN|      |      1 |     23 |     23 |00:00:00.01 |       5 |      |      |          |
|*  3 |    TABLE ACCESS FULL  | EMP  |      1 |      1 |      1 |00:00:00.01 |       3 |      |      |          |
|   4 |    BUFFER SORT        |      |      1 |     23 |     23 |00:00:00.01 |       2 | 2048 | 2048 | 2048 (0) |
|   5 |     TABLE ACCESS FULL | LOC  |      1 |     23 |     23 |00:00:00.01 |       2 |      |      |          |
|   6 |   TABLE ACCESS FULL   | DEPT |      1 |     27 |     27 |00:00:00.01 |       3 |      |      |          |
-----------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   1 - access("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID" AND "D"."LOCATION_ID"="L"."LOCATION_ID")
   3 - filter("E"."EMPLOYEE_ID"=100)

> e.employee_id = 100 비조인 조건
> 1건 찾는데 Full Scan, Used-Memory 사용

 

 

# Nested Loop Join 적용

HR@ora11g> select /*+ gather_plan_statistics leading(e,d,l) use_nl(d) use_nl(l) */ 
                  e.last_name, e.job_id, d.department_name, l.city
           from emp e, dept d, loc l
           where e.department_id = d.department_id
           and d.location_id = l.location_id
           and e.employee_id = 100;

LAST_NAME                 JOB_ID     DEPARTMENT_NAME                CITY
------------------------- ---------- ------------------------------ ------------------------------
King                      AD_PRES    Executive                      Seattle


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

--------------------------------------------------------------------------------------
| Id  | Operation           | Name | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
--------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT    |      |      1 |        |      1 |00:00:00.01 |      10 |
|   1 |  NESTED LOOPS       |      |      1 |      1 |      1 |00:00:00.01 |      10 |
|   2 |   NESTED LOOPS      |      |      1 |      1 |      1 |00:00:00.01 |       7 |
|*  3 |    TABLE ACCESS FULL| EMP  |      1 |      1 |      1 |00:00:00.01 |       4 |
|*  4 |    TABLE ACCESS FULL| DEPT |      1 |      1 |      1 |00:00:00.01 |       3 |
|*  5 |   TABLE ACCESS FULL | LOC  |      1 |      1 |      1 |00:00:00.01 |       3 |
--------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   3 - filter("E"."EMPLOYEE_ID"=100)
   4 - filter("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")
   5 - filter("D"."LOCATION_ID"="L"."LOCATION_ID")

> 버퍼 수 늘어남



# e.employee_id에 인덱스 설정 : 제약 조건과 분리

HR@ora11g> create unique index hr.emp_idx on hr.emp(employee_id);

Index created.

HR@ora11g> alter table hr.emp add constraint emp_pk primary key (employee_id) using index hr.emp_idx;

Table altered.


HR@ora11g> select a.table_name, a.constraint_name, b.column_name, a.constraint_type, a.search_condition, 
                  a.r_constraint_name, a.index_name
           from user_constraints a, user_cons_columns b
           where a.constraint_name = b.constraint_name
           and a.table_name in ('EMP');

TABLE_NAME           CONSTRAINT_NAME                COLUMN_NAME          C SEARCH_CONDITION          R_CONSTRAINT_NAME INDEX_NAME
-------------------- ------------------------------ -------------------- - ------------------------- ----------------- ----------
EMP                  SYS_C0011664                   LAST_NAME            C "LAST_NAME" IS NOT NULL
EMP                  SYS_C0011665                   EMAIL                C "EMAIL" IS NOT NULL
EMP                  SYS_C0011666                   HIRE_DATE            C "HIRE_DATE" IS NOT NULL
EMP                  SYS_C0011667                   JOB_ID               C "JOB_ID" IS NOT NULL
EMP                  EMP_PK                         EMPLOYEE_ID          P                                             EMP_IDX


HR@ora11g> select ix.index_name, ix.uniqueness, ic.column_name
           from user_indexes ix, user_ind_columns ic
           where ix.index_name = ic.index_name
           and ix.table_name = 'EMP';
           
INDEX_NAME                     UNIQUENES COLUMN_NAME
------------------------------ --------- --------------------
EMP_IDX                        UNIQUE    EMPLOYEE_ID


HR@ora11g> select /*+ gather_plan_statistics leading(e,d,l) use_nl(d) use_nl(l) */ 
                  e.last_name, e.job_id, d.department_name, l.city
           from emp e, dept d, loc l
           where e.department_id = d.department_id
           and d.location_id = l.location_id
           and e.employee_id = 100;

LAST_NAME                 JOB_ID     DEPARTMENT_NAME                CITY
------------------------- ---------- ------------------------------ ------------------------------
King                      AD_PRES    Executive                      Seattle


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

---------------------------------------------------------------------------------------------------
| Id  | Operation                     | Name    | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
---------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |         |      1 |        |      1 |00:00:00.01 |       8 |
|   1 |  NESTED LOOPS                 |         |      1 |      1 |      1 |00:00:00.01 |       8 |
|   2 |   NESTED LOOPS                |         |      1 |      1 |      1 |00:00:00.01 |       5 |
|   3 |    TABLE ACCESS BY INDEX ROWID| EMP     |      1 |      1 |      1 |00:00:00.01 |       2 |
|*  4 |     INDEX UNIQUE SCAN         | EMP_IDX |      1 |      1 |      1 |00:00:00.01 |       1 |
|*  5 |    TABLE ACCESS FULL          | DEPT    |      1 |      1 |      1 |00:00:00.01 |       3 |
|*  6 |   TABLE ACCESS FULL           | LOC     |      1 |      1 |      1 |00:00:00.01 |       3 |
---------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   4 - access("E"."EMPLOYEE_ID"=100)
   5 - filter("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")
   6 - filter("D"."LOCATION_ID"="L"."LOCATION_ID")



# d.department_id 인덱스 설정

HR@ora11g> create unique index hr.dept_idx on hr.dept(department_id);

Index created.

HR@ora11g> alter table hr.dept add constraint dept_pk primary key (department_id) using index hr.dept_idx;

Table altered.


HR@ora11g> select a.table_name, a.constraint_name, b.column_name, a.constraint_type, a.search_condition, a.r_constraint_name, a.index_name
           from user_constraints a, user_cons_columns b
           where a.constraint_name = b.constraint_name
           and a.table_name in ('DEPT');
           
TABLE_NAME           CONSTRAINT_NAME                COLUMN_NAME          C SEARCH_CONDITION          R_CONSTRAINT_NAME              INDEX_NAME
-------------------- ------------------------------ -------------------- - ------------------------- ------------------------------ ------------------------------
DEPT                 SYS_C0011668                   DEPARTMENT_NAME      C "DEPARTMENT_NAME" IS NOT NULL
DEPT                 DEPT_PK                        DEPARTMENT_ID        P                                                          DEPT_IDX


HR@ora11g> select ix.index_name, ix.uniqueness, ic.column_name
           from user_indexes ix, user_ind_columns ic
           where ix.index_name = ic.index_name
           and ix.table_name = 'DEPT';
           
INDEX_NAME                     UNIQUENES COLUMN_NAME
------------------------------ --------- --------------------
DEPT_IDX                       UNIQUE    DEPARTMENT_ID


HR@ora11g> select /*+ gather_plan_statistics leading(e,d,l) use_nl(d) use_nl(l) */ 
                  e.last_name, e.job_id, d.department_name, l.city
           from emp e, dept d, loc l
           where e.department_id = d.department_id
           and d.location_id = l.location_id
           and e.employee_id = 100;

LAST_NAME                 JOB_ID     DEPARTMENT_NAME                CITY
------------------------- ---------- ------------------------------ ------------------------------
King                      AD_PRES    Executive                      Seattle


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

----------------------------------------------------------------------------------------------------
| Id  | Operation                     | Name     | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
----------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |          |      1 |        |      1 |00:00:00.01 |       7 |
|   1 |  NESTED LOOPS                 |          |      1 |      1 |      1 |00:00:00.01 |       7 |
|   2 |   NESTED LOOPS                |          |      1 |      1 |      1 |00:00:00.01 |       4 |
|   3 |    TABLE ACCESS BY INDEX ROWID| EMP      |      1 |      1 |      1 |00:00:00.01 |       2 |
|*  4 |     INDEX UNIQUE SCAN         | EMP_IDX  |      1 |      1 |      1 |00:00:00.01 |       1 |
|   5 |    TABLE ACCESS BY INDEX ROWID| DEPT     |      1 |      1 |      1 |00:00:00.01 |       2 |
|*  6 |     INDEX UNIQUE SCAN         | DEPT_IDX |      1 |      1 |      1 |00:00:00.01 |       1 |
|*  7 |   TABLE ACCESS FULL           | LOC      |      1 |      1 |      1 |00:00:00.01 |       3 |
----------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   4 - access("E"."EMPLOYEE_ID"=100)
   6 - access("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")
   7 - filter("D"."LOCATION_ID"="L"."LOCATION_ID")



# l.location_id 인덱스 설정

HR@ora11g> create unique index hr.loc_idx on hr.loc(location_id);

Index created.

HR@ora11g> alter table hr.loc add constraint loc_pk primary key (location_id) using index hr.loc_idx;

Table altered.


HR@ora11g> select a.table_name, a.constraint_name, b.column_name, a.constraint_type, a.search_condition, a.r_constraint_name, a.index_name
           from user_constraints a, user_cons_columns b
           where a.constraint_name = b.constraint_name
           and a.table_name in ('LOC');

TABLE_NAME           CONSTRAINT_NAME                COLUMN_NAME          C SEARCH_CONDITION          R_CONSTRAINT_NAME              INDEX_NAME
-------------------- ------------------------------ -------------------- - ------------------------- ------------------------------ ------------------------------
LOC                  SYS_C0011669                   CITY                 C "CITY" IS NOT NULL
LOC                  LOC_PK                         LOCATION_ID          P                                                          LOC_IDX


HR@ora11g> select ix.index_name, ix.uniqueness, ic.column_name
           from user_indexes ix, user_ind_columns ic
           where ix.index_name = ic.index_name
           and ix.table_name = 'LOC';

INDEX_NAME                     UNIQUENES COLUMN_NAME
------------------------------ --------- --------------------
LOC_IDX                        UNIQUE    LOCATION_ID


HR@ora11g> select /*+ gather_plan_statistics leading(e,d,l) use_nl(d) use_nl(l) */ 
                  e.last_name, e.job_id, d.department_name, l.city
           from emp e, dept d, loc l
           where e.department_id = d.department_id
           and d.location_id = l.location_id
           and e.employee_id = 100;

LAST_NAME                 JOB_ID     DEPARTMENT_NAME                CITY
------------------------- ---------- ------------------------------ ------------------------------
King                      AD_PRES    Executive                      Seattle


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

----------------------------------------------------------------------------------------------------
| Id  | Operation                     | Name     | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
----------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |          |      1 |        |      1 |00:00:00.01 |       6 |
|   1 |  NESTED LOOPS                 |          |      1 |      1 |      1 |00:00:00.01 |       6 |
|   2 |   NESTED LOOPS                |          |      1 |      1 |      1 |00:00:00.01 |       4 |
|   3 |    TABLE ACCESS BY INDEX ROWID| EMP      |      1 |      1 |      1 |00:00:00.01 |       2 |
|*  4 |     INDEX UNIQUE SCAN         | EMP_IDX  |      1 |      1 |      1 |00:00:00.01 |       1 |
|   5 |    TABLE ACCESS BY INDEX ROWID| DEPT     |      1 |      1 |      1 |00:00:00.01 |       2 |
|*  6 |     INDEX UNIQUE SCAN         | DEPT_IDX |      1 |      1 |      1 |00:00:00.01 |       1 |
|   7 |   TABLE ACCESS BY INDEX ROWID | LOC      |      1 |      1 |      1 |00:00:00.01 |       2 |
|*  8 |    INDEX UNIQUE SCAN          | LOC_IDX  |      1 |      1 |      1 |00:00:00.01 |       1 |
----------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   4 - access("E"."EMPLOYEE_ID"=100)
   6 - access("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")
   8 - access("D"."LOCATION_ID"="L"."LOCATION_ID")

 

 

> NESTED LOOPS + INDEX 설계 필수

 

 

 

# 예시 2
HR@ora11g> select /*+ gather_plan_statistics */ e.last_name, e.job_id, d.department_name, l.city
           from emp e, dept d, loc l
           where e.department_id = d.department_id
           and d.location_id = l.location_id
           and l.city = 'Seattle';

18 rows selected.


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

------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                     | Name    | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |
------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |         |      1 |        |     18 |00:00:00.01 |       9 |       |       |          |
|*  1 |  HASH JOIN                    |         |      1 |     15 |     18 |00:00:00.01 |       9 |   967K|   967K| 1187K (0)|
|   2 |   MERGE JOIN                  |         |      1 |      4 |     21 |00:00:00.01 |       4 |       |       |          |
|*  3 |    TABLE ACCESS BY INDEX ROWID| LOC     |      1 |      1 |      1 |00:00:00.01 |       2 |       |       |          |
|   4 |     INDEX FULL SCAN           | LOC_IDX |      1 |     23 |     23 |00:00:00.01 |       1 |       |       |          |
|*  5 |    SORT JOIN                  |         |      1 |     27 |     21 |00:00:00.01 |       2 |  2048 |  2048 | 2048  (0)|
|   6 |     TABLE ACCESS FULL         | DEPT    |      1 |     27 |     27 |00:00:00.01 |       2 |       |       |          |
|   7 |   TABLE ACCESS FULL           | EMP     |      1 |    107 |    107 |00:00:00.01 |       5 |       |       |          |
------------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   1 - access("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")
   3 - filter("L"."CITY"='Seattle')
   5 - access("D"."LOCATION_ID"="L"."LOCATION_ID")
       filter("D"."LOCATION_ID"="L"."LOCATION_ID")



# Nested Loop Join 적용

HR@ora11g> select count(*) from loc where city = 'Seattle';

  COUNT(*)
----------
         1

HR@ora11g> select /*+ gather_plan_statistics leading(l,d,e) use_nl(d) use_nl(e) */ 
                  e.last_name, e.job_id, d.department_name, l.city
           from emp e, dept d, loc l
           where e.department_id = d.department_id
           and d.location_id = l.location_id
           and l.city = 'Seattle';

18 rows selected.


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

--------------------------------------------------------------------------------------
| Id  | Operation           | Name | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
--------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT    |      |      1 |        |     18 |00:00:00.01 |      72 |
|   1 |  NESTED LOOPS       |      |      1 |     15 |     18 |00:00:00.01 |      72 |
|   2 |   NESTED LOOPS      |      |      1 |      4 |     21 |00:00:00.01 |       7 |
|*  3 |    TABLE ACCESS FULL| LOC  |      1 |      1 |      1 |00:00:00.01 |       3 |
|*  4 |    TABLE ACCESS FULL| DEPT |      1 |      4 |     21 |00:00:00.01 |       4 |
|*  5 |   TABLE ACCESS FULL | EMP  |     21 |      4 |     18 |00:00:00.01 |      65 |
--------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   3 - filter("L"."CITY"='Seattle')
   4 - filter("D"."LOCATION_ID"="L"."LOCATION_ID")
   5 - filter("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")

> outer : loc
> inner : dept

 

 

# 인덱스 설정

HR@ora11g> create index hr.loc_city_idx on hr.loc(city);

Index created.

HR@ora11g> create index hr.dept_loc_idx on hr.dept(location_id);

Index created.

Index HR@ora11g> create index hr.emp_dept_idx on hr.emp(department_id);

Index created.


HR@ora11g> select ix.index_name, ix.uniqueness, ic.column_name
           from user_indexes ix, user_ind_columns ic
           where ix.index_name = ic.index_name
           and ix.table_name in ('EML','DEPT','LOC');

INDEX_NAME                     UNIQUENES COLUMN_NAME
------------------------------ --------- --------------------
DEPT_IDX                       UNIQUE    DEPARTMENT_ID
DEPT_LOC_IDX                   NONUNIQUE LOCATION_ID
LOC_IDX                        UNIQUE    LOCATION_ID
LOC_CITY_IDX                   NONUNIQUE CITY


HR@ora11g> select /*+ gather_plan_statistics leading(l,d,e) use_nl(d) use_nl(e) */ 
e.last_name, e.job_id, d.department_name, l.city
from emp e, dept d, loc l
where e.department_id = d.department_id
and d.location_id = l.location_id
and l.city = 'Seattle';

18 rows selected.


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

---------------------------------------------------------------------------------------------------------
| Id  | Operation                      | Name         | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
---------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT               |              |      1 |        |     18 |00:00:00.01 |      18 |
|   1 |  NESTED LOOPS                  |              |      1 |     15 |     18 |00:00:00.01 |      18 |
|   2 |   NESTED LOOPS                 |              |      1 |     40 |     18 |00:00:00.01 |      15 |
|   3 |    NESTED LOOPS                |              |      1 |      4 |     21 |00:00:00.01 |       9 |
|   4 |     TABLE ACCESS BY INDEX ROWID| LOC          |      1 |      1 |      1 |00:00:00.01 |       3 |
|*  5 |      INDEX RANGE SCAN          | LOC_CITY_IDX |      1 |      1 |      1 |00:00:00.01 |       2 |
|   6 |     TABLE ACCESS BY INDEX ROWID| DEPT         |      1 |      4 |     21 |00:00:00.01 |       6 |
|*  7 |      INDEX RANGE SCAN          | DEPT_LOC_IDX |      1 |      4 |     21 |00:00:00.01 |       3 |
|*  8 |    INDEX RANGE SCAN            | EMP_DEPT_IDX |     21 |     10 |     18 |00:00:00.01 |       6 |
|   9 |   TABLE ACCESS BY INDEX ROWID  | EMP          |     18 |      4 |     18 |00:00:00.01 |       3 |
---------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   5 - access("L"."CITY"='Seattle')
   7 - access("D"."LOCATION_ID"="L"."LOCATION_ID")
   8 - access("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")

> 3 + 8 -> JOIN -> 실행계획 상 batch I/O 발생

 

 

# Inner쪽 테이블에 대해 Batch I/O가 발생안되게 하자(Table Prefetch)

HR@ora11g> select /*+ gather_plan_statistics leading(l,d,e) use_nl(d) no_nlj_batching(e) */
                  e.last_name, e.job_id, d.department_name, l.city
           from emp e, dept d, loc l
           where e.department_id = d.department_id
           and d.location_id = l.location_id
           and l.city = 'Seattle';

18 rows selected.


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

---------------------------------------------------------------------------------------------------------
| Id  | Operation                      | Name         | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
---------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT               |              |      0 |        |      0 |00:00:00.01 |       0 |
|   1 |  TABLE ACCESS BY INDEX ROWID   | EMP          |      1 |      4 |     18 |00:00:00.01 |      18 |
|   2 |   NESTED LOOPS                 |              |      1 |     40 |     40 |00:00:00.01 |      15 |
|   3 |    NESTED LOOPS                |              |      1 |      4 |     21 |00:00:00.01 |       9 |
|   4 |     TABLE ACCESS BY INDEX ROWID| LOC          |      1 |      1 |      1 |00:00:00.01 |       3 |
|*  5 |      INDEX RANGE SCAN          | LOC_CITY_IDX |      1 |      1 |      1 |00:00:00.01 |       2 |
|   6 |     TABLE ACCESS BY INDEX ROWID| DEPT         |      1 |      4 |     21 |00:00:00.01 |       6 |
|*  7 |      INDEX RANGE SCAN          | DEPT_LOC_IDX |      1 |      4 |     21 |00:00:00.01 |       3 |
|*  8 |    INDEX RANGE SCAN            | EMP_DEPT_IDX |     21 |     10 |     18 |00:00:00.01 |       6 |
---------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   5 - access("L"."CITY"='Seattle')
   7 - access("D"."LOCATION_ID"="L"."LOCATION_ID")
   8 - access("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")

> INDEX RANGE SCAN : 하나씩 I/O
> EMP(Inner)가 8번 위가 아닌 NESTED LOOPS 위에 있다?
 -> Table Prefetch : 메모리에 데이터가 없어 디스크에서 데이터를 한번에 작업
> 차라리 조인해서 결과셋을 한번에 올리기 -> 진화한게 Batch I/O : 물리적 I/O 시, 메모리 없을 때 효과적
> Cluster Factor가 좋으면 Buffer Pinning 발생하여 더 효율적

- Multi Block I/O : Full Scan으로 데이터가 연속일 때 작업

 

 

 

# 예시 3
HR@ora11g> select /*+ gather_plan_statistics */ e.last_name, e.job_id, d.department_name, l.city
           from emp e, dept d, loc l
           where e.department_id = d.department_id
           and d.location_id = l.location_id
           and l.city = 'Seattle'
           and e.job_id = 'AD_VP';

LAST_NAME                 JOB_ID     DEPARTMENT_NAME                CITY
------------------------- ---------- ------------------------------ ------------------------------
Kochhar                   AD_VP      Executive                      Seattle
De Haan                   AD_VP      Executive                      Seattle


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

---------------------------------------------------------------------------------------------------------
| Id  | Operation                      | Name         | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
---------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT               |              |      1 |        |      2 |00:00:00.01 |      14 |
|   1 |  NESTED LOOPS                  |              |      1 |      1 |      2 |00:00:00.01 |      14 |
|   2 |   NESTED LOOPS                 |              |      1 |     40 |     18 |00:00:00.01 |      12 |
|   3 |    NESTED LOOPS                |              |      1 |      4 |     21|00:00:00.01 |       7 |
|   4 |     TABLE ACCESS BY INDEX ROWID| LOC          |      1 |      1 |      1 |00:00:00.01 |       3 |
|*  5 |      INDEX RANGE SCAN          | LOC_CITY_IDX |      1 |      1 |      1 |00:00:00.01 |       2 |
|   6 |     TABLE ACCESS BY INDEX ROWID| DEPT         |      1 |      4 |     21 |00:00:00.01 |       4 |
|*  7 |      INDEX RANGE SCAN          | DEPT_LOC_IDX |      1 |      4 |     21 |00:00:00.01 |       2 |
|*  8 |    INDEX RANGE SCAN            | EMP_DEPT_IDX |     21 |     10 |     18 |00:00:00.01 |       5 |
|*  9 |   TABLE ACCESS BY INDEX ROWID  | EMP          |     18 |      1 |      2 |00:00:00.01 |       2 |
---------------------------------------------------------------------------------------------------------

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

   5 - access("L"."CITY"='Seattle')
   7 - access("D"."LOCATION_ID"="L"."LOCATION_ID")
   8 - access("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")
   9 - filter("E"."JOB_ID"='AD_VP')

> 결과는 2건 > 불필요한 I/O가 많다 > 처음부터 2건을 찾자

 

 

# Index + Nested Loop Join

HR@ora11g> create index hr.emp_job_idx on hr.emp(job_id);

Index created.

HR@ora11g> select ix.index_name, ix.uniqueness, ic.column_name
           from user_indexes ix, user_ind_columns ic
           where ix.index_name = ic.index_name
           and ix.table_name in ('EMP','DEPT','LOC');

INDEX_NAME                     UNIQUENES COLUMN_NAME
------------------------------ --------- --------------------
DEPT_IDX                       UNIQUE    DEPARTMENT_ID
DEPT_LOC_IDX                   NONUNIQUE LOCATION_ID
EMP_IDX                        UNIQUE    EMPLOYEE_ID
EMP_DEPT_IDX                   NONUNIQUE DEPARTMENT_ID
EMP_JOB_IDX                    NONUNIQUE JOB_ID
LOC_IDX                        UNIQUE    LOCATION_ID
LOC_CITY_IDX                   NONUNIQUE CITY


HR@ora11g> select /*+ gather_plan_statistics leading(e,d,l) use_nl(d) use_nl(l) */ 
                  e.last_name, e.job_id, d.department_name, l.city
           from emp e, dept d, loc l
           where e.department_id = d.department_id
           and d.location_id = l.location_id
           and l.city = 'Seattle'
           and e.job_id = 'AD_VP';

2 rows selected.


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

---------------------------------------------------------------------------------------------------------
| Id  | Operation                      | Name         | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
---------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT               |              |      1 |        |      2 |00:00:00.01 |      13 |
|   1 |  NESTED LOOPS                  |              |      1 |      1 |      2 |00:00:00.01 |      13 |
|   2 |   NESTED LOOPS                 |              |      1 |      6 |      2 |00:00:00.01 |      11 |
|   3 |    NESTED LOOPS                |              |      1 |      6 |      2 |00:00:00.01 |       8 |
|   4 |     TABLE ACCESS BY INDEX ROWID| EMP          |      1 |      6 |      2 |00:00:00.01 |       4 |
|*  5 |      INDEX RANGE SCAN          | EMP_JOB_IDX  |      1 |      6 |      2 |00:00:00.01 |       2 |
|   6 |     TABLE ACCESS BY INDEX ROWID| DEPT         |      2 |      1 |      2 |00:00:00.01 |       4 |
|*  7 |      INDEX UNIQUE SCAN         | DEPT_IDX     |      2 |      1 |      2 |00:00:00.01 |       2 |
|*  8 |    INDEX RANGE SCAN            | LOC_CITY_IDX |      2 |      1 |      2 |00:00:00.01 |       3 |
|*  9 |   TABLE ACCESS BY INDEX ROWID  | LOC          |      2 |      1 |      2 |00:00:00.01 |       2 |
---------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   5 - access("E"."JOB_ID"='AD_VP')
   7 - access("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")
   8 - access("L"."CITY"='Seattle')
   9 - filter("D"."LOCATION_ID"="L"."LOCATION_ID")


> 8번 LOC_CITY_IDX가 9번 LOC 테이블 위에 위치하면서 3번과 조인? -> Batch I/O 발생

 


# Nested Loop Join + Inner 테이블의 Index 변경 : use_nl_with_index(l loc_idx)

HR@ora11g> select /*+ gather_plan_statistics leading(e,d,l) use_nl(d) use_nl(l) use_nl_with_index(l loc_idx) */ 
                  e.last_name, e.job_id, d.department_name, l.city
           from emp e, dept d, loc l
           where e.department_id = d.department_id
           and d.location_id = l.location_id
           and l.city = 'Seattle'
           and e.job_id = 'AD_VP';

2 rows selected.


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

--------------------------------------------------------------------------------------------------------
| Id  | Operation                      | Name        | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
--------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT               |             |      1 |        |      2 |00:00:00.01 |      12 |
|   1 |  NESTED LOOPS                  |             |      1 |      1 |      2 |00:00:00.01 |      12 |
|   2 |   NESTED LOOPS                 |             |      1 |      6 |      2 |00:00:00.01 |      10 |
|   3 |    NESTED LOOPS                |             |      1 |      6 |      2 |00:00:00.01 |       8 |
|   4 |     TABLE ACCESS BY INDEX ROWID| EMP         |      1 |      6 |      2 |00:00:00.01 |       4 |
|*  5 |      INDEX RANGE SCAN          | EMP_JOB_IDX |      1 |      6 |      2 |00:00:00.01 |       2 |
|   6 |     TABLE ACCESS BY INDEX ROWID| DEPT        |      2 |      1 |      2 |00:00:00.01 |       4 |
|*  7 |      INDEX UNIQUE SCAN         | DEPT_IDX    |      2 |      1 |      2 |00:00:00.01 |       2 |
|*  8 |    INDEX UNIQUE SCAN           | LOC_IDX     |      2 |      1 |      2 |00:00:00.01 |       2 |
|*  9 |   TABLE ACCESS BY INDEX ROWID  | LOC         |      2 |      1 |      2 |00:00:00.01 |       2 |
--------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   5 - access("E"."JOB_ID"='AD_VP')
   7 - access("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")
   8 - access("D"."LOCATION_ID"="L"."LOCATION_ID")
   9 - filter("L"."CITY"='Seattle')

 


                    1
                  /   \
   조인결과 집합→ 2     9
               /   \
조인결과 집합→ 3     8
            /   \
           4     6
           |     |
           5     7



# 예시 4

 

# leading(e,d) use_nl(d)

HR@ora11g> select /*+ gather_plan_statistics leading(e,d) use_nl(d) */
                  e.employee_id, e.job_id, d.department_name
           from employees e, departments d
           where e.department_id = d.department_id;

106 rows selected.


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

------------------------------------------------------------------------------------------------------
| Id  | Operation                    | Name        | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |             |      1 |        |    106 |00:00:00.01 |     129 |
|   1 |  NESTED LOOPS                |             |      1 |    106 |    106 |00:00:00.01 |     129 |
|   2 |   NESTED LOOPS               |             |      1 |    107 |    106 |00:00:00.01 |      23 |
|   3 |    TABLE ACCESS FULL         | EMPLOYEES   |      1 |    107 |    107 |00:00:00.01 |      13 |
|*  4 |    INDEX UNIQUE SCAN         | DEPT_ID_PK  |    107 |      1 |    106 |00:00:00.01 |      10 |
|   5 |   TABLE ACCESS BY INDEX ROWID| DEPARTMENTS |    106 |      1 |    106 |00:00:00.01 |     106 |
------------------------------------------------------------------------------------------------------

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

   4 - access("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")


> d.department_id : outer : 중복성이 없는 컬럼



# leading(d,e) use_nl(e)

HR@ora11g> select /*+ gather_plan_statistics leading(d,e) use_nl(e) */ 
                  e.employee_id, e.job_id, d.department_name
           from employees e, departments d
           where e.department_id = d.department_id;
 
 
HR@ora11g> select * from table(dbms_xplan.display_cursor(null,null,'allstats last'));

------------------------------------------------------------------------------------------------------------
| Id  | Operation                    | Name              | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |                   |      1 |        |    106 |00:00:00.01 |      37 |
|   1 |  NESTED LOOPS                |                   |      1 |    106 |    106 |00:00:00.01 |      37 |
|   2 |   NESTED LOOPS               |                   |      1 |    270 |    106 |00:00:00.01 |      24 |
|   3 |    TABLE ACCESS FULL         | DEPARTMENTS       |      1 |     27 |     27 |00:00:00.01 |      11 |
|*  4 |    INDEX RANGE SCAN          | EMP_DEPARTMENT_IX |     27 |     10 |    106 |00:00:00.01 |      13 |
|   5 |   TABLE ACCESS BY INDEX ROWID| EMPLOYEES         |    106 |      4 |    106 |00:00:00.01 |      13 |
------------------------------------------------------------------------------------------------------------

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

   4 - access("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")


> Buffer Pinning 작동
> 애당초 비조인성격이 없는 조인조건문은 Nested Loop Join으로 하면 안된다.






■ Sort Merge Join
- 조인되는 건수가 많을 때 유리하다.
- sort에 대한 성능 문제가 발생할 수 있다.
- use_merge(), leading()

- 단계
    1. 각각의 조인 키 컬럼을 기준으로 양쪽 집합을 정렬한다.
    2. 정렬된 양쪽 집합을 서로 merge 한다.

- First, Second

- sort_area_size

 

# 예시 5
HR@ora11g> select /*+ gather_plan_statistics */ e.employee_id, e.job_id, d.department_name
           from employees e, departments d
           where e.department_id = d.department_id;
 
 
HR@ora11g> select * from table(dbms_xplan.display_cursor(null,null,'allstats last'));

-------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                    | Name        | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  OMem |1Mem | Used-Mem |
-------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |             |      1 |        |    106 |00:00:00.01 |      18 |       |     |          |
|   1 |  MERGE JOIN                  |             |      1 |    106 |    106 |00:00:00.01 |      18 |       |     |          |
|   2 |   TABLE ACCESS BY INDEX ROWID| DEPARTMENTS |      1 |     27 |     27 |00:00:00.01 |      12 |       |     |          |
|   3 |    INDEX FULL SCAN           | DEPT_ID_PK  |      1 |     27 |     27 |00:00:00.01 |       6 |       |     |          |
|*  4 |   SORT JOIN                  |             |     27 |    107 |    106 |00:00:00.01 |       6 |  6144 |6144 | 6144  (0)|
|   5 |    TABLE ACCESS FULL         | EMPLOYEES   |      1 |    107 |    107 |00:00:00.01 |       6 |       |     |          |
-------------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   4 - access("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")
       filter("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")


> 2 : First
> 4 : Second

↓

First Table
HR@ora11g> select department_id, department_name
           from departments
           order by department_id;

Second Table : department_id 중복성이 많은 곳이라 범위검색하기 좋다
HR@ora11g> select department_id, employee_id, job_id
           from employees
           order by department_id;



# leading(e,d) use_merge(d)

HR@ora11g> select /*+ gather_plan_statistics leading(e,d) use_merge(d) */ 
                  e.employee_id, e.job_id, d.department_name
           from employees e, departments d
           where e.department_id = d.department_id;
 
106 rows selected.


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

------------------------------------------------------------------------------------------------------------------------
| Id  | Operation           | Name        | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |
------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT    |             |      1 |        |    106 |00:00:00.01 |      12 |       |       |          |
|   1 |  MERGE JOIN         |             |      1 |    106 |    106 |00:00:00.01 |      12 |       |       |          |
|   2 |   SORT JOIN         |             |      1 |    107 |    107 |00:00:00.01 |       6 |  6144 |  6144 | 6144  (0)|
|   3 |    TABLE ACCESS FULL| EMPLOYEES   |      1 |    107 |    107 |00:00:00.01 |       6 |       |       |          |
|*  4 |   SORT JOIN         |             |    107 |     27 |    106 |00:00:00.01 |       6 |  2048 |  2048 | 2048  (0)|
|   5 |    TABLE ACCESS FULL| DEPARTMENTS |      1 |     27 |     27 |00:00:00.01 |       6 |       |       |          |
------------------------------------------------------------------------------------------------------------------------

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

   4 - access("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")
       filter("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")


> 버퍼는 줄었지만 메모리 사용량이 늘어 남



# leading(e,d,l) use_merge(d) use_merge(l)

HR@ora11g> select /*+ gather_plan_statistics leading(e,d,l) use_merge(d) use_merge(l) */ 
                  e.employee_id, e.job_id, d.department_name, l.city, l.street_address
           from employees e, departments d, locations l
           where e.department_id = d.department_id
           and d.location_id = l.location_id;
 
106 rows selected.


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

--------------------------------------------------------------------------------------------------------------------------
| Id  | Operation             | Name        | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |
--------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |             |      1 |        |    106 |00:00:00.01 |      18 |       |       |          |
|   1 |  MERGE JOIN           |             |      1 |    106 |    106 |00:00:00.01 |      18 |       |       |          |
|   2 |   SORT JOIN           |             |      1 |    106 |    106 |00:00:00.01 |      12 |  9216 |  9216 | 8192  (0)|
|   3 |    MERGE JOIN         |             |      1 |    106 |    106 |00:00:00.01 |      12 |       |       |          |
|   4 |     SORT JOIN         |             |      1 |    107 |    107 |00:00:00.01 |       6 |  6144 |  6144 | 6144  (0)|
|   5 |      TABLE ACCESS FULL| EMPLOYEES   |      1 |    107 |    107 |00:00:00.01 |       6 |       |       |          |
|*  6 |     SORT JOIN         |             |    107 |     27 |    106 |00:00:00.01 |       6 |  2048 |  2048 | 2048  (0)|
|   7 |      TABLE ACCESS FULL| DEPARTMENTS |      1 |     27 |     27 |00:00:00.01 |       6 |       |       |          |
|*  8 |   SORT JOIN           |             |    106 |     23 |    106 |00:00:00.01 |       6 |  2048 |  2048 | 2048  (0)|
|   9 |    TABLE ACCESS FULL  | LOCATIONS   |      1 |     23 |     23 |00:00:00.01 |       6 |       |       |          |
--------------------------------------------------------------------------------------------------------------------------

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

   6 - access("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")
       filter("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")
   8 - access("D"."LOCATION_ID"="L"."LOCATION_ID")
       filter("D"."LOCATION_ID"="L"."LOCATION_ID")


> 중복성있는 테이블을 우선으로하면 sort join 또 수행한다



# leading(l,d,e) use_merge(d) use_merge(e)

HR@ora11g> select /*+ gather_plan_statistics leading(l,d,e) use_merge(d) use_merge(e) */ 
                  e.employee_id, e.job_id, d.department_name, l.city, l.street_address
           from employees e, departments d, locations l
           where e.department_id = d.department_id
           and d.location_id = l.location_id;
 
106 rows selected.


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

-----------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                      | Name        | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |
-----------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT               |             |      1 |        |    106 |00:00:00.01 |      14 |       |       |          |
|   1 |  MERGE JOIN                    |             |      1 |    106 |    106 |00:00:00.01 |      14 |       |       |          |
|   2 |   SORT JOIN                    |             |      1 |     27 |     27 |00:00:00.01 |       8 |  2048 |  2048 | 2048  (0)|
|   3 |    MERGE JOIN                  |             |      1 |     27 |     27 |00:00:00.01 |       8 |       |       |          |
|   4 |     TABLE ACCESS BY INDEX ROWID| LOCATIONS   |      1 |     23 |     19 |00:00:00.01 |       2 |       |       |          |
|   5 |      INDEX FULL SCAN           | LOC_ID_PK   |      1 |     23 |     19 |00:00:00.01 |       1 |       |       |          |
|*  6 |     SORT JOIN                  |             |     19 |     27 |     27 |00:00:00.01 |       6 |  2048 |  2048 | 2048  (0)|
|   7 |      TABLE ACCESS FULL         | DEPARTMENTS |      1 |     27 |     27 |00:00:00.01 |       6 |       |       |          |
|*  8 |   SORT JOIN                    |             |     27 |    107 |    106 |00:00:00.01 |       6 |  6144 |  6144 | 6144  (0)|
|   9 |    TABLE ACCESS FULL           | EMPLOYEES   |      1 |    107 |    107 |00:00:00.01 |       6 |       |       |          |
-----------------------------------------------------------------------------------------------------------------------------------

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

   6 - access("D"."LOCATION_ID"="L"."LOCATION_ID")
       filter("D"."LOCATION_ID"="L"."LOCATION_ID")
   8 - access("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")
       filter("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")


> LOC_ID_PK 인덱스로 정렬이 되어 있어 Sort Join 대신 Full Scan

 

 

 

# leading(l,d,e) use_nl(d) use_nl(e)

HR@ora11g> select /*+ gather_plan_statistics leading(l,d,e) use_nl(d) use_nl(e) */ 
                  e.employee_id, e.job_id, d.department_name, l.city, l.street_address
           from employees e, departments d, locations l
           where e.department_id = d.department_id
           and d.location_id = l.location_id;
 
106 rows selected.


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

--------------------------------------------------------------------------------------------------------------
| Id  | Operation                      | Name              | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
--------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT               |                   |      1 |        |    106 |00:00:00.01 |      56 |
|   1 |  NESTED LOOPS                  |                   |      1 |    106 |    106 |00:00:00.01 |      56 |
|   2 |   NESTED LOOPS                 |                   |      1 |    270 |    106 |00:00:00.01 |      41 |
|   3 |    NESTED LOOPS                |                   |      1 |     27 |     27 |00:00:00.01 |      27 |
|   4 |     TABLE ACCESS FULL          | LOCATIONS         |      1 |     23 |     23 |00:00:00.01 |      11 |
|   5 |     TABLE ACCESS BY INDEX ROWID| DEPARTMENTS       |     23 |      1 |     27 |00:00:00.01 |      16 |
|*  6 |      INDEX RANGE SCAN          | DEPT_LOCATION_IX  |     23 |      4 |     27 |00:00:00.01 |      11 |
|*  7 |    INDEX RANGE SCAN            | EMP_DEPARTMENT_IX |     27 |     10 |    106 |00:00:00.01 |      14 |
|   8 |   TABLE ACCESS BY INDEX ROWID  | EMPLOYEES         |    106 |      4 |    106 |00:00:00.01 |      15 |
--------------------------------------------------------------------------------------------------------------

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

   6 - access("D"."LOCATION_ID"="L"."LOCATION_ID")
   7 - access("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")

 

 

 

# 예시 6
HR@ora11g> drop table hr.emp purge;

Table dropped.

HR@ora11g> create table hr.emp nologging
           as select rownum emp_id, last_name, first_name, hire_date, job_id, salary, department_id
              from hr.employees, (select rownum emp_id from dual connect by level <= 1000);

Table created.



# leading(e,d) use_merge(d)

HR@ora11g> select /*+ gather_plan_statistics leading(e,d) use_merge(d) */ 
                  e.emp_id, e.job_id, d.department_name
           from emp e, dept d
           where e.department_id = d.department_id;
 
106000 rows selected.


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

-----------------------------------------------------------------------------------------------------------------------
| Id  | Operation           | Name | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads | OMem | 1Mem | Used-Mem |
-----------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT    |      |      1 |        |    106K|00:00:00.16 |     688 |   683 |      |      |          |
|   1 |  MERGE JOIN         |      |      1 |    118K|    106K|00:00:00.16 |     688 |   683 |      |      |          |
|   2 |   SORT JOIN         |      |      1 |    119K|    106K|00:00:00.11 |     686 |   683 | 3809K|  839K| 3385K (0)|
|   3 |    TABLE ACCESS FULL| EMP  |      1 |    119K|    107K|00:00:00.06 |     686 |   683 |      |      |          |
|*  4 |   SORT JOIN         |      |    106K|     27 |    106K|00:00:00.03 |       2 |     0 | 2048 | 2048 | 2048  (0)|
|   5 |    TABLE ACCESS FULL| DEPT |      1 |     27 |     27 |00:00:00.01 |       2 |     0 |      |      |          |
-----------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   4 - access("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")
       filter("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")


> 시간 소요 > 악성코드의 지름길

 


# leading(d,e) use_merge(e)

HR@ora11g> select /*+ gather_plan_statistics leading(d,e) use_merge(e) */ 
                  e.emp_id, e.job_id, d.department_name
           from emp e, dept d
           where e.department_id = d.department_id;
 
106000 rows selected.


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

------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                    | Name     | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads | OMem | 1Mem | Used-Mem |
------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |          |      1 |        |    106K|00:00:00.05 |     710 |   683 |      |      |          |
|   1 |  MERGE JOIN                  |          |      1 |    118K|    106K|00:00:00.05 |     710 |   683 |      |      |          |
|   2 |   TABLE ACCESS BY INDEX ROWID| DEPT     |      1 |     27 |     27 |00:00:00.01 |      24 |     0 |      |      |          |
|   3 |    INDEX FULL SCAN           | DEPT_IDX |      1 |     27 |     27 |00:00:00.01 |      12 |     0 |      |      |          |
|*  4 |   SORT JOIN                  |          |     27 |    119K|    106K|00:00:00.04 |     686 |   683 | 3809K|  839K| 3385K (0)|
|   5 |    TABLE ACCESS FULL         | EMP      |      1 |    119K|    107K|00:00:00.01 |     686 |   683 |      |      |          |
------------------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   4 - access("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")
       filter("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")


> 버퍼는 늘었지만 시간 단축이 더 효율적



# leading(e,l,d) use_merge(l) use_merge(d)

HR@ora11g> select /*+ gather_plan_statistics leading(e,l,d) use_merge(l) use_merge(d) */ 
                  e.emp_id, e.job_id, d.department_name, l.city, l.street_address
           from emp e, dept d, loc l
           where e.department_id = d.department_id
           and d.location_id = l.location_id;

106000 rows selected.


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

------------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation              | Name | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads | Writes | OMem | 1Mem | Used-Mem | Used-Tmp |
------------------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT       |      |      1 |        |    106K|00:00:14.69 |    1058 | 18805 |  18185 |      |      |          |          |
|   1 |  MERGE JOIN            |      |      1 |    118K|    106K|00:00:14.69 |    1058 | 18805 |  18185 |      |      |          |          |
|   2 |   SORT JOIN            |      |      1 |   2753K|   2438K|00:00:15.00 |    1056 | 18805 |  18185 |  159M| 4260K|   47M (1)|      143K|
|   3 |    MERGE JOIN CARTESIAN|      |      1 |   2753K|   2461K|00:00:00.42 |     688 |   683 |      0 |      |      |          |          |
|   4 |     TABLE ACCESS FULL  | EMP  |      1 |    119K|    107K|00:00:00.02 |     686 |   683 |      0 |      |      |          |          |
|   5 |     BUFFER SORT        |      |    107K|     23 |   2461K|00:00:00.19 |       2 |     0 |      0 | 2048 | 2048 | 2048  (0)|          |
|   6 |      TABLE ACCESS FULL | LOC  |      1 |     23 |     23 |00:00:00.01 |       2 |     0 |      0 |      |      |          |          |
|*  7 |   SORT JOIN            |      |   2438K|     27 |    106K|00:00:00.46 |       2 |     0 |      0 | 2048 | 2048 | 2048  (0)|          |
|   8 |    TABLE ACCESS FULL   | DEPT |      1 |     27 |     27 |00:00:00.01 |       2 |     0 |      0 |      |      |          |          |
------------------------------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   7 - access("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID" AND "D"."LOCATION_ID"="L"."LOCATION_ID")
       filter(("D"."LOCATION_ID"="L"."LOCATION_ID" AND "E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID"))

 


# leading(d,l,e) use_merge(l) use_merge(e)

HR@ora11g> select /*+ gather_plan_statistics leading(d,l,e) use_merge(l) use_merge(e) */ 
                  e.emp_id, e.job_id, d.department_name, l.city, l.street_address
           from emp e, dept d, loc l
           where e.department_id = d.department_id
           and d.location_id = l.location_id;
 
106000 rows selected.


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

---------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                      | Name         | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads | OMem | 1Mem | Used-Mem |
---------------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT               |              |      1 |        |    106K|00:00:00.08 |     690 |   683 |      |      |          |
|   1 |  MERGE JOIN                    |              |      1 |    118K|    106K|00:00:00.08 |     690 |   683 |      |      |          |
|   2 |   SORT JOIN                    |              |      1 |     27 |     27 |00:00:00.01 |       4 |     0 | 2048 | 2048 | 2048  (0)|
|   3 |    MERGE JOIN                  |              |      1 |     27 |     27 |00:00:00.01 |       4 |     0 |      |      |          |
|   4 |     TABLE ACCESS BY INDEX ROWID| DEPT         |      1 |     27 |     27 |00:00:00.01 |       2 |     0 |      |      |          |
|   5 |      INDEX FULL SCAN           | DEPT_LOC_IDX |      1 |     27 |     27 |00:00:00.01 |       1 |     0 |      |      |          |
|*  6 |     SORT JOIN                  |              |     27 |     23 |     27 |00:00:00.01 |       2 |     0 | 2048 | 2048 | 2048  (0)|
|   7 |      TABLE ACCESS FULL         | LOC          |      1 |     23 |     23 |00:00:00.01 |       2 |     0 |      |      |          |
|*  8 |   SORT JOIN                    |              |     27 |    119K|    106K|00:00:00.06 |     686 |   683 | 3809K|  839K| 3385K (0)|
|   9 |    TABLE ACCESS FULL           | EMP          |      1 |    119K|    107K|00:00:00.01 |     686 |   683 |      |      |          |
---------------------------------------------------------------------------------------------------------------------------------------------

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

   6 - access("D"."LOCATION_ID"="L"."LOCATION_ID")
       filter("D"."LOCATION_ID"="L"."LOCATION_ID")
   8 - access("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")
       filter("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")



# leading(l,d,e) use_merge(d) use_merge(e)

HR@ora11g> select /*+ gather_plan_statistics leading(l,d,e) use_merge(d) use_merge(e) */ 
                  e.emp_id, e.job_id, d.department_name, l.city, l.street_address
           from emp e, dept d, loc l
           where e.department_id = d.department_id
           and d.location_id = l.location_id;
 
106000 rows selected.


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

----------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                      | Name    | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads | OMem | 1Mem | Used-Mem |
----------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT               |         |      1 |        |    106K|00:00:00.07 |     690 |   683 |      |      |          |
|   1 |  MERGE JOIN                    |         |      1 |    118K|    106K|00:00:00.07 |     690 |   683 |      |      |          |
|   2 |   SORT JOIN                    |         |      1 |     27 |     27 |00:00:00.01 |       4 |     0 | 2048 | 2048 | 2048  (0)|
|   3 |    MERGE JOIN                  |         |      1 |     27 |     27 |00:00:00.01 |       4 |     0 |      |      |          |
|   4 |     TABLE ACCESS BY INDEX ROWID| LOC     |      1 |     23 |     19 |00:00:00.01 |       2 |     0 |      |      |          |
|   5 |      INDEX FULL SCAN           | LOC_IDX |      1 |     23 |     19 |00:00:00.01 |       1 |     0 |      |      |          |
|*  6 |     SORT JOIN                  |         |     19 |     27 |     27 |00:00:00.01 |       2 |     0 | 2048 | 2048 | 2048  (0)|
|   7 |      TABLE ACCESS FULL         | DEPT    |      1 |     27 |     27 |00:00:00.01 |       2 |     0 |      |      |          |
|*  8 |   SORT JOIN                    |         |     27 |    119K|    106K|00:00:00.06 |     686 |   683 | 3809K|  839K| 3385K (0)|
|   9 |    TABLE ACCESS FULL           | EMP     |      1 |    119K|    107K|00:00:00.01 |     686 |   683 |      |      |          |
----------------------------------------------------------------------------------------------------------------------------------------

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

   6 - access("D"."LOCATION_ID"="L"."LOCATION_ID")
       filter("D"."LOCATION_ID"="L"."LOCATION_ID")
   8 - access("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")
       filter("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")

 


# leading(l,d,e) use_merge(d) use_merge(e) -> TRACE

HR@ora11g> alter session set tracefile_identifier = 'merge';

Session altered.


HR@ora11g> alter session set events '10046 trace name context forever, level 8';

Session altered.


HR@ora11g> select /*+ gather_plan_statistics leading(l,d,e) use_merge(d) use_merge(e)*/ 
                  e.emp_id, e.job_id, d.department_name, l.city, l.street_address
           from emp e, dept d, loc l
           where e.department_id = d.department_id
           and d.location_id = l.location_id;
 
106000 rows selected.


HR@ora11g> alter session set events '10046 trace name context off';

Session altered.


HR@ora11g> !

[oracle@oracle diag]$ cd $ORACLE_BASE/diag/rdbms/ora11g/ora11g/trace

[oracle@oracle trace]$ ls *merge.trc
ora11g_ora_30198_merge.trc

[oracle@oracle trace]$ tkprof ora11g_ora_30198_merge.trc merge_30198.txt sys=no

[oracle@oracle trace]$ cat merge_30198.txt
...
call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          1          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch     7068      0.17       0.17        683        690          0      106000
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     7070      0.17       0.18        683        691          0      106000

Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 84
Number of plan statistics captured: 1

Rows (1st) Rows (avg) Rows (max)  Row Source Operation
---------- ---------- ----------  ---------------------------------------------------
    106000     106000     106000  MERGE JOIN  (cr=690 pr=683 pw=0 time=149943 us cost=1267 size=10072075 card=118495)
        27         27         27   SORT JOIN (cr=4 pr=0 pw=0 time=66 us cost=7 size=1404 card=27)
        27         27         27    MERGE JOIN  (cr=4 pr=0 pw=0 time=94 us cost=6 size=1404 card=27)
        19         19         19     TABLE ACCESS BY INDEX ROWID LOC (cr=2 pr=0 pw=0 time=32 us cost=2 size=759 card=23)
        19         19         19      INDEX FULL SCAN LOC_IDX (cr=1 pr=0 pw=0 time=8 us cost=1 size=0 card=23)(object id 88842)
        27         27         27     SORT JOIN (cr=2 pr=0 pw=0 time=48 us cost=4 size=513 card=27)
        27         27         27      TABLE ACCESS FULL DEPT (cr=2 pr=0 pw=0 time=11 us cost=3 size=513 card=27)
    106000     106000     106000   SORT JOIN (cr=686 pr=683 pw=0 time=88567 us cost=1260 size=3951189 card=119733)
    107000     107000     107000    TABLE ACCESS FULL EMP (cr=686 pr=683 pw=0 time=7808 us cost=193 size=3951189 card=119733)


Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  SQL*Net message to client                    7068        0.00          0.00
  direct path read                               18        0.00          0.00
  SQL*Net message from client                  7068        1.12          5.18
********************************************************************************