정렬처리도 해시 조인과 같이 효율성 기준으로 optimal. onepass, multipass과 같이 3가지로 구분한다
정렬 중에 디스크로 덤프되는 데이터 포맷을 보면 쓰여지는 전체 양이 생각 보다는 훨씬 더 크다.
본 사례를 보면 디스크로 덤프된 량이 몇 블록인데(몇 mb)인데 실제 정렬된 순수 데이터 부분은 몇 mb크기에 불과하다
필자의 가설 : 오라클 정렬메커니즘의 의한 결과라는 유추
: 일종의 이진삽입트리에 기초로 한다( 이진 트리는 하위노드가 2두개인 트리구조이다)
: 여튼 workarea_policy = auto이며 pga_aggregate_target 디폴트 200MB를 사용한 경우 샘플코드를 수행하면서 최적의 메모리를 사용한다.
머지조인을 보통 sort merge join이라고 하는 것은 조인의 양쪽 대상 집합 모두가 조인 컬럼 순으로 정렬되어야 하기 때문이다.
해시 조인 처럼 조인을 두개의 독립된 쿼리로 분해하고 결과집합을 다시 결합하는 것이다.
그러나 해시 조인과는 달리 두 번째 쿼리 부분을 시작하기 전에 첫 번째 쿼리부분을 끝내야할 필요는 없다.(정렬을 한다는 것은 두 집합이 각각 실행완료된것을 의미하지 않을까?)
1. 액세스와 필터 조건절을 사용하여 첫 번째 데이터 집합을 획득하고 그것을 조인 컬럼 순으로 정렬한다.
2. 액세스와 필터 조건절을 사용하여 두 번째 데이터 집합을 획득하고 그것을 조인 컬럼 순으로 정렬한다.
3. 첫 번째 데이터 집합의 각 로우에 대해 두 번째 데이터 집합에서 시작점을 찾고 조인에 실패하는 로우를 만날 때까지 스캔한다.(각각 집합이 정렬되어 있으므로 중단지점을 알 수 있다.)
적절한 인덱스가 있으면 두 번째 집합을 획득하기 전에 전체를 처리하지 않아도된다.
머지조인은 첫번째 집합을 정렬할 필요가 없다.
-> : 정렬준비가 완료된 후에라야 조인을 시작할 수 있으므로 원초적으로 부분범위처리를 할 수 없어 항상 전체범위처리를 한다
: 동시적으로 처리된다. 조인 대상 집합은 각자가 상대 집합의 처리결과와 상관없이 자신이 보유한 처리조건만 가지고 액세스하여 정렬해 둔다. 양쪽 집합이 모두 준비가 완료되어야만 머지를 시작할 수 있으므로 순차적인 처리가 불가능하다
http://wiki.gurubee.net/pages/viewpage.action?pageId=1966755
alter session set hash_join_enabled = false;
create table t1 as
with generator as (
select --+ materialize
rownum id
from all_objects
where rownum <= 3000
)
select rownum id,
rownum n1,
trunc((rownum - 1)/2) n2,
lpad(rownum,10,'0') small_vc,
rpad('x',100,'x') padding
from generator v1,
generator v2
where rownum <= 10000;
alter table t1 add constraint t1_pk primary key(id);
select count(distinct t1_vc || t2_vc)
from (select /*+ no_merge ordered use_merge(t2) */
t1.small_vc t1_vc
t2.small_vc t2_vc
from t1,
t2,
where
t1.n1 <= 1000 and t2.id = t1.id -- 1번
t1.n1 <= 1000 and t2.n2 = t1.id -- 2번
t1.n1 <= 1000 and t2.id between t1.id -1 and t1.id +1 -- 3번
t1.n1 <= 1000 and t2.n2 = t1.id -- 4번
t1.n1 <= 1000 and t2.id = t1.id -- 5번
);
집계query 수행 시 기본적인 실행계획
---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 26 | 3 (34)| 00:00:01 |
| 1 | SORT ORDER BY | | 1 | 26 | 3 (34)| 00:00:01 |
| 2 | TABLE ACCESS FULL| T1 | 1 | 26 | 2 (0)| 00:00:01 |
---------------------------------------------------------------------------
각 문장에 따른 변화는 id =1번의 오퍼레이션이 조금씩 바뀌지만 골격은 바뀌지 않는다.
단 10g부터는 Hash Group by(Hash unique)오퍼레이션이 있는 데 대부분의 경우 sort group by보다 빠르게 수행된나 기존 정렬의 효과는 없다.
select col1, col2 from t1 order by col1, col2;
| 1 | SORT ORDER BY | | 1 | 26 | 3 (34)| 00:00:01 |
select distinct col1, col2 from t1;
| 1 | HASH UNIQUE | | 1 | 26 | 3 (34)| 00:00:01 | <- 책은 Sort (unique)
select col1, col2, count(*) from t1 group by col1, col2;
| 1 | SORT GROUP BY | | 1 | 26 | 3 (34)| 00:00:01 |
select col1, col2, count(*) from t1 group by col1, col2 order by col1, col2;
| 1 | SORT GROUP BY | | 1 | 26 | 3 (34)| 00:00:01 |
select max(col1), max(col2) from t1;
| 1 | SORT AGGREGATE | | 1 | 26 | | |
Union(합집합) : 양측 집합 중 어느 쪽에든 존재하는 로우
Intersect(교집합) : 양측 집합 모두에 존재하는 로우
Minus(차집합) : 첫 번째 집합 중 두 번째 집합에 없는 로우
alter session set "_optimizer_cost_model"=io
drop table t1 purge;
create table t1 as
select rownum id, ao.*
from all_objects ao
where rownum <= 2500;
drop table t2 purge;
create table t2 as
select rownum id, ao.*
from all_objects ao
where rownum <= 2000;
- 각각 집합에 대한 유일성을 강제한 후 집합연산을 하는 경우의 쿼리
select distinct owner, object_type from t1
intersect
select distinct owner, object_type from t2;
- 집합연산자는 unique한 결과를 반환한다는 점을 반영한 쿼리
select owner, object_type from t1
intersect
select owner, object_type from t2;
--위 쿼리
-----------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost
-----------------------------------------------------------
| 0 | SELECT STATEMENT | | 2000 | 123K| 45
| 1 | INTERSECTION | | | |
| 2 | SORT UNIQUE | | 2500 | 70000 | 24
| 3 | TABLE ACCESS FULL| T1 | 2500 | 70000 | 5
| 4 | SORT UNIQUE | | 2000 | 56000 | 21
| 5 | TABLE ACCESS FULL| T2 | 2000 | 56000 | 4
----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2000 | 123K| 20 (50)| 00:00:01 |
| 1 | INTERSECTION | | | | | |
| 2 | SORT UNIQUE | | 2500 | 70000 | 11 (10)| 00:00:01 |
| 3 | TABLE ACCESS FULL| T1 | 2500 | 70000 | 10 (0)| 00:00:01 |
| 4 | SORT UNIQUE | | 2000 | 56000 | 9 (12)| 00:00:01 |
| 5 | TABLE ACCESS FULL| T2 | 2000 | 56000 | 8 (0)| 00:00:01 |
----------------------------------------------------------------------------
--아래 쿼리
------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost |
------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2000 | 123K| 45 |
| 1 | INTERSECTION | | | | |
| 2 | SORT UNIQUE | | 2500 | 70000 | 24 |
| 3 | TABLE ACCESS FULL| T1 | 2500 | 70000 | 5 |
| 4 | SORT UNIQUE | | 2000 | 56000 | 21 |
| 5 | TABLE ACCESS FULL| T2 | 2000 | 56000 | 4 |
------------------------------------------------------------
----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2000 | 123K| 20 (50)| 00:00:01 |
| 1 | INTERSECTION | | | | | |
| 2 | SORT UNIQUE | | 2500 | 70000 | 11 (10)| 00:00:01 |
| 3 | TABLE ACCESS FULL| T1 | 2500 | 70000 | 10 (0)| 00:00:01 |
| 4 | SORT UNIQUE | | 2000 | 56000 | 9 (12)| 00:00:01 |
| 5 | TABLE ACCESS FULL| T2 | 2000 | 56000 | 8 (0)| 00:00:01 |
----------------------------------------------------------------------------
alter session set "_optimizer_cost_model"=cpu;
alter session set statistics_level = all;
alter session set optimizer_features_enable = '9.2.0';
explain plan for
create table t_intersect as
select distinct *
from ( select owner, object_type from t1
intersect
select owner, object_type from t2
);
select * from table(dbms_xplan.display());
----------------------------------------------
| Id | Operation | Name |
----------------------------------------------
| 0 | CREATE TABLE STATEMENT | |
| 1 | LOAD AS SELECT | T_INTERSECT |
| 2 | SORT UNIQUE | |
| 3 | VIEW | |
| 4 | INTERSECTION | |
| 5 | SORT UNIQUE | |
| 6 | TABLE ACCESS FULL| T1 |
| 7 | SORT UNIQUE | |
| 8 | TABLE ACCESS FULL| T2 |
----------------------------------------------
alter session set optimizer_features_enable = '10.2.0.1';
explain plan for
create table t_intersect as
select distinct *
from ( select owner, object_type from t1
intersect
select owner, object_type from t2
);
select * from table(dbms_xplan.display());
--------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------
| 0 | CREATE TABLE STATEMENT | | 2000 | 56000 | 30 (10)| 00:00:01 |
| 1 | LOAD AS SELECT | T_INTERSECT | | | | |
| 2 | HASH UNIQUE | | 2000 | 56000 | 27 (12)| 00:00:01 |
| 3 | VIEW | | 2000 | 56000 | 26 (8)| 00:00:01 |
| 4 | INTERSECTION | | | | | |
| 5 | SORT UNIQUE | | 2500 | 70000 | 11 (10)| 00:00:01 |
| 6 | TABLE ACCESS FULL| T1 | 2500 | 70000 | 10 (0)| 00:00:01 |
| 7 | SORT UNIQUE | | 2000 | 56000 | 9 (12)| 00:00:01 |
| 8 | TABLE ACCESS FULL| T2 | 2000 | 56000 | 8 (0)| 00:00:01 |
--------------------------------------------------------------------------------------
9i와 10g에서 같은 쿼리를 실행하였을 때 9i에서 불필요한 오퍼레이션을 보여주지 않았지만 10g에서는 불필요한 연산에 대한 부분이 나온다.
그러나 9i실행계획이 잘못된 것이며 실제 10032트레이로 확인 하면 9i도 10g와 같은 중간연산을 하는 것을 확인할 수 있다.(위 테스트에서는 적용되지 않음)
런타임 시에 엔진이 수행하는 것이 옵티마이저가 비용계산 시에 생각한 그대로일 필요는 없다는 것이다.
: max intermediate merge width multi-pass 정렬을 해야할 때, 오라클이 한 번에 읽고 머지할 수 있는 sort run의 수
: number of merges 스트림머지를 한 수
: Inital runs 오라클이 추산한 디스크로 덤프하게 될 sort runs의 개수(Block to sort * 블록크기 / area size)로 계산된다.\
: Number of direct sync 직접읽기대기회수
: Number of direct writes 직접쓰기대기회수 (오류로 정확하지 않은것 같다.)
: sort_with = max intermediate merge width
: Merge passes 전체 데이터 집합이 디스크로 쓰여졌다가 다시 읽혀지는 횟수이다.
inital runs가 Sort width보다 적으면 한번의 pass로 머지됨을 의미한다.
: Area size 데이터처리목적으로 사용된 메모리양(pga_aggregate_target에 기초한 최소할당양)
: Max Area size 해당 세션에서 받을 수 있는 최대 할당양
: Degree 병렬처리외 직렬처리 시 각각의 정렬비용계산내역을 별도의 섹션으로 보여준다.
: Rows 테이블의 계산된(필터링된) 카디널리티이다.
Area_size를 늘이지 않고도 Row갯수를 떨어뜨릴 수 있기 때문에 병렬처리를 이용하면 정렬비용을 손쉽게 낮출 수 있다.
: Block to sort 정렬될 데이터양으로 "로우크기*로우개수/블록크기"로 산출된다.
: Row size 옵티마이져가 추산한 정렬 데이터의 평균 row길이이다.
: Total IO sort cost '아마도' cost per pass와 pass회수가 결합된 값이다.
: Total CPU sort cost 비용중 CPU에 해당하는 부분으로서 CPU오퍼레이션 수 단위이고, 트레이스파일을 보면 나중에 상응하는 비교적 작은 I/O비용으로 전환한다.
: Total Temp space used 정렬 오퍼레이션이 요구하는 임시 영역의 이론적인 크기