1. OR-Expansion
or 조건을 분해해서 union-all 형태로 변환해주는 기능
/*+Use_concat*/ : or조건을 분해하여 union-all 로 변환시킨다
/*+No_expand*/ : or expansion을 방지한다.
##or /*+No_expand*/ = /*+INLIST ITERATOR*/ 를 사용한 예
explain plan for
select * from orders where row_id = 31806
or row_id = 34000;
select * from table(dbms_xplan.dispaly());
Plan hash value: 1010959076
----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2 | 454 | 5 (0)| 00:00:01 |
| 1 | INLIST ITERATOR | | | | | |
| 2 | TABLE ACCESS BY INDEX ROWID BATCHED| ORDERS | 2 | 454 | 5 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | IDX_ORDERS_ROW_ID | 2 | | 3 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - access("ROW_ID"=31806 OR "ROW_ID"=34000)
##union all /*+Use_concat*/ 을 사용한 예
explain plan for
select * from orders where row_id = 31806
union all
select * from orders where row_id = 34000;
Plan hash value: 757051961
----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2 | 454 | 4 (0)| 00:00:01 |
| 1 | UNION-ALL | | | | | |
| 2 | TABLE ACCESS BY INDEX ROWID BATCHED| ORDERS | 1 | 227 | 2 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | IDX_ORDERS_ROW_ID | 1 | | 1 (0)| 00:00:01 |
| 4 | TABLE ACCESS BY INDEX ROWID BATCHED| ORDERS | 1 | 227 | 2 (0)| 00:00:01 |
|* 5 | INDEX RANGE SCAN | IDX_ORDERS_ROW_ID | 1 | | 1 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - access("ROW_ID"=31806)
or cost 는 5
union all 은 4
union all 이 이득이다.
row_id 가 등가로 인한 선택도가 매우 낮기 때문에, index 를 사용하여 합치는 것이 cost 가 적게 든다.
2. Subquery Unnesting
subquey unnesting 은 서브쿼리가 사용된 sql 명령문을 join 문법으로 변형시켜서 실행한다.
--sub unnesting 사용
explain plan for
select * from orders where order_id in
(select order_id from returns);
--------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2404 | 568K| 486 (1)| 00:00:01 |
|* 1 | HASH JOIN RIGHT SEMI| | 2404 | 568K| 486 (1)| 00:00:01 |
| 2 | TABLE ACCESS FULL | RETURNS | 1173 | 17595 | 5 (0)| 00:00:01 |
| 3 | TABLE ACCESS FULL | ORDERS | 51290 | 11M| 480 (1)| 00:00:01 |
--------------------------------------------------------------------------------
##Hash join이 사용된 것을 알 수 있다.
--hash join을 사용하지 않게 변형
explain plan for
select * from orders where order_id in
(select /*+no_unnest*/order_id from returns);
------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2 | 454 | 236K (1)| 00:00:10 |
|* 1 | FILTER | | | | | |
| 2 | TABLE ACCESS FULL| ORDERS | 51290 | 11M| 480 (1)| 00:00:01 |
|* 3 | TABLE ACCESS FULL| RETURNS | 1 | 15 | 5 (0)| 00:00:01 |
------------------------------------------------------------------------------
3. View merging
쿼리문을 블럭화 하기 위해 서브쿼리, 인라인 뷰 등을 자주 사용한다.
오라클 옵티마이저가 인라인 뷰, 서브쿼리를 해석할 때 독자적으로 실행하지 않고 메인쿼리와 함께 실행되는 경우,
즉 쿼리 블록을 풀어서 기존 쿼리와 함께 최적화를 수행하는 것을 뷰 머징(VIEW MERGING)이라 한다.
옵티마이저는 뷰 머징 전후, 두 개의 실행계획을 비교하여 비용이 적은 SQL 명령문을 실행하게 된다.
옵티마이저 입장에서는 뷰는 매우 불편한 sql 명령문을 만들어내기 때문에 되도록 뷰 머징을 수행할려고 한다.
뷰 머징을 수행해보고 안된다면 조건절 pushing 을 수행한다.
예시)
Employee 테이블에서는 Departments테이블과 Department_id 로 조인
Locations 테이블에서는 Departments테이블과 Location_id 로 조인을 수행할거다.
select E.first_name, E.last_name, dept_locs_v.street_address, dept_locs_v.postal_code
from employees E, ( select d.department_id, d.department_name, l.street_address, l.postal_code from departments d, locations l where d.location_id = l.location_id) dept_locs_v
where dept_locs_v.department_id = E.department_id and E.last_name = 'Smith';
==========================================================================================
Lindsey Smith Magdalen Centre, The Oxford Science Park OX9 9ZB
William Smith Magdalen Centre, The Oxford Science Park OX9 9ZB
explain plan for
select E.first_name, E.last_name, dept_locs_v.street_address, dept_locs_v.postal_code
from employees E, ( select d.department_id, d.department_name, l.street_address, l.postal_code from departments d, locations l where d.location_id = l.location_id) dept_locs_v
where dept_locs_v.department_id = E.department_id and E.last_name = 'Smith';
Plan hash value: 1618608772
----------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 56 | 4 (0)| 00:00:01 |
| 1 | NESTED LOOPS | | | | | |
| 2 | NESTED LOOPS | | 1 | 56 | 4 (0)| 00:00:01 |
| 3 | NESTED LOOPS | | 1 | 25 | 3 (0)| 00:00:01 |
| 4 | TABLE ACCESS BY INDEX ROWID| EMPLOYEES | 1 | 18 | 2 (0)| 00:00:01 |
|* 5 | INDEX RANGE SCAN | EMP_NAME_IX | 1 | | 1 (0)| 00:00:01 |
| 6 | TABLE ACCESS BY INDEX ROWID| DEPARTMENTS | 1 | 7 | 1 (0)| 00:00:01 |
|* 7 | INDEX UNIQUE SCAN | DEPT_ID_PK | 1 | | 0 (0)| 00:00:01 |
|* 8 | INDEX UNIQUE SCAN | LOC_ID_PK | 1 | | 0 (0)| 00:00:01 |
| 9 | TABLE ACCESS BY INDEX ROWID | LOCATIONS | 1 | 31 | 1 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------
3번의 조인이 발생했다.
위는 인라인 뷰를 사용한 조인문이며, 만약 인라인뷰 없이 쿼리를 작성한다면 cost는 동일하게 나올 까?
위의 쿼리와는 다르고 값을 동일하게 추출해 봤을 때,
explain plan for
select E.first_name, E.last_name, l.street_address, l.postal_code
from employees E, departments d, locations l
where d.department_id = E.department_id and d.location_id = l.location_id
and E.last_name = 'Smith';
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 56 | 4 (0)| 00:00:01 |
| 1 | NESTED LOOPS | | | | | |
| 2 | NESTED LOOPS | | 1 | 56 | 4 (0)| 00:00:01 |
| 3 | NESTED LOOPS | | 1 | 25 | 3 (0)| 00:00:01 |
| 4 | TABLE ACCESS BY INDEX ROWID| EMPLOYEES | 1 | 18 | 2 (0)| 00:00:01 |
|* 5 | INDEX RANGE SCAN | EMP_NAME_IX | 1 | | 1 (0)| 00:00:01 |
| 6 | TABLE ACCESS BY INDEX ROWID| DEPARTMENTS | 1 | 7 | 1 (0)| 00:00:01 |
|* 7 | INDEX UNIQUE SCAN | DEPT_ID_PK | 1 | | 0 (0)| 00:00:01 |
|* 8 | INDEX UNIQUE SCAN | LOC_ID_PK | 1 | | 0 (0)| 00:00:01 |
| 9 | TABLE ACCESS BY INDEX ROWID | LOCATIONS | 1 | 31 | 1 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------
코스트는 동일하게 나오나, 사람 관점으로 봤을 때는 인라인뷰가 더 보기 편할 수도 있다.
뷰머징도 동일한 관점으로 보는것...
-- 뷰머징이 없을 경우는 어떻게 될 것인가?
** from 인라인뷰 절에 넣는다
explain plan for
select E.first_name, E.last_name, dept_locs_v.street_address, dept_locs_v.postal_code
from employees E, ( select /*+no_merge*/ d.department_id, d.department_name, l.street_address, l.postal_code from departments d, locations l where d.location_id = l.location_id) dept_locs_v
where dept_locs_v.department_id = E.department_id and E.last_name = 'Smith';
---------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 61 | 8 (25)| 00:00:01 |
|* 1 | HASH JOIN | | 1 | 61 | 8 (25)| 00:00:01 |
| 2 | TABLE ACCESS BY INDEX ROWID | EMPLOYEES | 1 | 18 | 2 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | EMP_NAME_IX | 1 | | 1 (0)| 00:00:01 |
| 4 | VIEW | | 27 | 1161 | 6 (34)| 00:00:01 |
| 5 | MERGE JOIN | | 27 | 1026 | 6 (34)| 00:00:01 |
| 6 | TABLE ACCESS BY INDEX ROWID| LOCATIONS | 23 | 713 | 2 (0)| 00:00:01 |
| 7 | INDEX FULL SCAN | LOC_ID_PK | 23 | | 1 (0)| 00:00:01 |
|* 8 | SORT JOIN | | 27 | 189 | 4 (50)| 00:00:01 |
| 9 | VIEW | index$_join$_003 | 27 | 189 | 3 (34)| 00:00:01 |
|* 10 | HASH JOIN | | | | | |
| 11 | INDEX FAST FULL SCAN | DEPT_ID_PK | 27 | 189 | 1 (0)| 00:00:01 |
| 12 | INDEX FAST FULL SCAN | DEPT_LOCATION_IX | 27 | 189 | 1 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------------
cost 값이 뷰머징을 사용했을 경우 4 이며, 사용안할 경우 8 이 나온 것을 알 수 있다.
뷰머징을 사용하는 것이 이득이다.
뷰머지가 불가능한 조건
- 집합 연산자(union, union all, intersect, minus)
- connect by 절
- RowNum pseudo 컬럼
- select-list에 집계함수 사용 (group by없이 전체를 집계하는 경우를 말함)
- 분석 함수
'SQLP' 카테고리의 다른 글
[SQLP] 서브쿼리와 성능 (0) | 2024.08.21 |
---|---|
[SQLP] 쿼리 변환2 (0) | 2024.08.20 |
[SQLP] 데이터베이스 call 과 네트워크 부하 ★★★★★ (0) | 2024.08.17 |
[SQLP] 테이블 스페이스 (0) | 2024.08.15 |
[SQLP] 커서 공유 (0) | 2024.08.15 |