구분 | 설명 |
---|---|
메모리(In-Memory) 소트 | 전체 데이터의 정렬 작업을 할당받은 소트 영역 내에서 완료하는 것을 말함. 'Internal Sort' 또는 'Optimal Sort'라고도 함. |
디스크(To-Disk) 소트 | 할당받은 소트 영역 내에서 정렬을 완료하지 못해 디스크 공간까지 사용하는 것을 말함. 'External Sort'라고도 함. * Onepass Sort : 정렬 대상 집합을 디스크에 한 번만 기록 * Multipass Sort : 정렬 대상 집합을 디스크에 여러 번 기록 |
1) Sort Aggregate
전체 로우를 대상으로 집계를 수행할 때 나타남.
실제 소트가 발생하진 않는다.
SQL Server 실행계획엔 'Stream Aggregate' 라고 표시됨.
select sum(sal), max(sal), min(sal) from emp
---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 4 | 3 (0)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | 4 | | |
| 2 | TABLE ACCESS FULL| EMP | 14 | 56 | 3 (0)| 00:00:01 |
---------------------------------------------------------------------------
2) Sort Order By
정렬된 결과집합을 얻고자 할 때 나타남.
select * from emp order by sal desc
---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 14 | 518 | 4 (25)| 00:00:01 |
| 1 | SORT ORDER BY | | 14 | 518 | 4 (25)| 00:00:01 |
| 2 | TABLE ACCESS FULL| EMP | 14 | 518 | 3 (0)| 00:00:01 |
---------------------------------------------------------------------------
3) Sort Group By
Sorting 알고리즘을 사용해 그룹별 집계를 수행할 때 나타남.
오라클은 Hashing 알고리즘으로 그룹별 집계를 수행하기도 함.
select deptno, job, sum(sal), max(sal), min(sal)
from emp
group by deptno, job
---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 11 | 165 | 4 (25)| 00:00:01 |
| 1 | HASH GROUP BY | | 11 | 165 | 4 (25)| 00:00:01 |
| 2 | TABLE ACCESS FULL| EMP | 14 | 210 | 3 (0)| 00:00:01 |
---------------------------------------------------------------------------
4) Sort Unique
선택된 결과집합에서 중복 레코드를 제거하고자 할 때 나타남.
Union 연산자나 Distinct 연산자를 사용할 때가 대표적임.
select distinct deptno from emp order by deptno
---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 3 | 9 | 5 (40)| 00:00:01 |
| 1 | SORT UNIQUE | | 3 | 9 | 4 (25)| 00:00:01 |
| 2 | TABLE ACCESS FULL| EMP | 14 | 42 | 3 (0)| 00:00:01 |
---------------------------------------------------------------------------
5) Sort Join
Sort Merge Join을 수행할 때 나타남.
select /*+ ordered use_merge(e) */ *
from emp e, dept d
where d. deptno = e. deptno
----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 14 | 798 | 6 (17)| 00:00:01 |
| 1 | MERGE JOIN | | 14 | 798 | 6 (17)| 00:00:01 |
| 2 | TABLE ACCESS BY INDEX ROWID| EMP | 14 | 518 | 2 (0)| 00:00:01 |
| 3 | INDEX FULL SCAN | EMP_IDX | 14 | | 1 (0)| 00:00:01 |
|* 4 | SORT JOIN | | 4 | 80 | 4 (25)| 00:00:01 |
| 5 | TABLE ACCESS FULL | DEPT | 4 | 80 | 3 (0)| 00:00:01 |
----------------------------------------------------------------------------------------
6) Window Sort
윈도우 함수를 수행할 때 나타남.
select empno, ename, job, mgr, sal, row_number() over (order by hiredate)
from emp
---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 14 | 476 | 4 (25)| 00:00:01 |
| 1 | WINDOW SORT | | 14 | 476 | 4 (25)| 00:00:01 |
| 2 | TABLE ACCESS FULL| EMP | 14 | 476 | 3 (0)| 00:00:01 |
---------------------------------------------------------------------------
자주 사용되는 데이터 액세스 패턴을 고려하지 않은 채 물리 설계를 진행하거나,
M:M 관계의 테이블을 해소하지 않아 핵심 프로그램이 항상 소트 오퍼레이션을 수반하는 경우 등.
select 과금.고객id
, 과금.상품id
, 과금.과금액
, 가입상품.가입일시
from 과금
,(select 고객id
, 상품id
, min(가입일시) 가입일시
from 고객별상품라인
group by 고객id, 상품id) 가입상품
where 과금.고객id(+) = 가입상품.고객id
and 과금.상품id(+) = 가입상품.상품id
and 과금.과금연월(+) = :yyyymm
-- 잘 정규화된 데이터 모델을 사용했을 때
select 과금.고객id
, 과금.상품id
, 과금.과금액
, 가입상품.가입일시
from 과금
, 가입상품
where 과금.고객id(+) = 가입상품.고객id
and 과금.상품id(+) = 가입상품.상품id
and 과금.과금연월(+) = :yyyymm
select empno, job, mgr from emp where deptno = 10
union
select empno, job, mgr from emp where deptno = 20;
-----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 8 | 152 | 6 (67)| 00:00:01 |
| 1 | SORT UNIQUE | | 8 | 152 | 6 (67)| 00:00:01 |
| 2 | UNION-ALL | | | | | |
| 3 | TABLE ACCESS BY INDEX ROWID| EMP | 3 | 57 | 2 (0)| 00:00:01 |
|* 4 | INDEX RANGE SCAN | EMP_IDX | 3 | | 1 (0)| 00:00:01 |
| 5 | TABLE ACCESS BY INDEX ROWID| EMP | 5 | 95 | 2 (0)| 00:00:01 |
|* 6 | INDEX RANGE SCAN | EMP_IDX | 5 | | 1 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------
select empno, job, mgr from emp where deptno = 10
union all
select empno, job, mgr from emp where deptno = 20;
----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 8 | 152 | 4 (50)| 00:00:01 |
| 1 | UNION-ALL | | | | | |
| 2 | TABLE ACCESS BY INDEX ROWID| EMP | 3 | 57 | 2 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | EMP_IDX | 3 | | 1 (0)| 00:00:01 |
| 4 | TABLE ACCESS BY INDEX ROWID| EMP | 5 | 95 | 2 (0)| 00:00:01 |
|* 5 | INDEX RANGE SCAN | EMP_IDX | 5 | | 1 (0)| 00:00:01 |
----------------------------------------------------------------------------------------
select distinct 과금연월
from 과금
where 과금연월 <= :yyyymm
and 지역 like :reg || '%'
select 연월
from 연월테이블 a
where 연월 <= :yyyymm
and exists (select 'x'
from 과금
where 과금연월 = a.연월
and 지역 like :reg || '%'
)
declare
l_cnt number;
begin
select count(*) into l_cnt
from member
where memb_cls = '1'
and birth_yyyy <= '1950';
if l_cnt > 0 then
dbms_output.put_line('exists');
else
dbms_output.put_line('not exists');
end if;
end;
declare
l_cnt number;
begin
select 1 into l_cnt
from member
where memb_cls = '1'
and birth_yyyy <= '1950'
and rownum <= 1;
dbms_output.put_line('exists');
exception
when no_data_found then
dbms_output.put_line('not exists');
end;
declare @cnt int
select @cnt = count(*)
where exists (select 'x'
from member
where mem_cls = '1'
and birth_yyyy <= '1950'
)
if @cnt > 0
print 'exists'
else
print 'not exists'
select custid, name, resno, status, tel1
from customer
where region = 'A'
order by custid
select region, avg(age), count(*)
from customer
group by region
drop table t1;
create table t1 as
select rownum as c1
, mod(rownum,3) + 1 as c2
, mod(rownum,5) + 1 as c3
from dual
connect by level <= 100
;
select *
from t1
where c2 = '3'
order by c3
;
---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 33 | 1287 | 4 (25)| 00:00:01 |
| 1 | SORT ORDER BY | | 33 | 1287 | 4 (25)| 00:00:01 |
|* 2 | TABLE ACCESS FULL| T1 | 33 | 1287 | 3 (0)| 00:00:01 |
---------------------------------------------------------------------------
-- Sort Order By 대체
create index t1_n1 on t1(c2,c3);
-------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 33 | 1287 | 2 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| T1 | 33 | 1287 | 2 (0)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | T1_N1 | 33 | | 1 (0)| 00:00:01 |
-------------------------------------------------------------------------------------
-- Sort Group By 대체
-- (Hash Group By 로 나옴)
select c2
, count(*)
from t1
group by c2
;
---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 100 | 1300 | 4 (25)| 00:00:01 |
| 1 | HASH GROUP BY | | 100 | 1300 | 4 (25)| 00:00:01 |
| 2 | TABLE ACCESS FULL| T1 | 100 | 1300 | 3 (0)| 00:00:01 |
---------------------------------------------------------------------------
select nvl(max(주문번호), 0) + 1
from 주문
where 주문일자 = :주문일자
-- FIRST ROW 와 MIN/MAX 오퍼레이션이 나타남
create index emp_n1 on emp(job, empno);
select nvl(max(empno),0) + 1
from emp
where job = 'SALESMAN'
;
---------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 12 | 1 (0)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | 12 | | |
| 2 | FIRST ROW | | 4 | 48 | 1 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN (MIN/MAX)| EMP_N1 | 4 | 48 | 1 (0)| 00:00:01 |
---------------------------------------------------------------------------------------
-- 주의) max 함수 내에서 인덱스 컬럼을 가공하면 인덱스를 사용하지 못하게 될 수 있음.
-- SQL Server는 인덱스를 정상적으로 사용함.
select nvl(max(empno + 1),1)
from emp
where job = 'SALESMAN'
;
----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 12 | 1 (0)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | 12 | | |
|* 2 | INDEX RANGE SCAN| EMP_N1 | 4 | 48 | 1 (0)| 00:00:01 |
----------------------------------------------------------------------------
-- 1번 쿼리
select lpad(empno, 10) || lpad(ename, 30) || lpad(job, 30)
from emp
where sal <= 2000
order by job
-- 2번 쿼리
select lpad(empno, 10) || lpad(ename, 30) || lpad(job, 30)
from (select empno
, ename
, job
from emp
where sal <= 2000
order by job)
-- SQL Server, Sybase
select top 10 거래일시, 체결건수, 체결수량, 거래대금
from 시간별종목거래
where 종목코드 = 'KR123456'
and 거래일시 >= '20080304'
-- IBM DB2
select 거래일시, 체결건수, 체결수량, 거래대금
from 시간별종목거래
where 종목코드 = 'KR123456'
and 거래일시 >= '20080304'
order by 거래일시
fetch first 10 rows only
-- Oracle
select *
from (select 거래일시, 체결건수, 체결수량, 거래대금
from 시간별종목거래
where 종목코드 = 'KR123456'
and 거래일시 >= '20080304'
order by 거래일시
)
where rownum <= 10
[] Top-N 쿼리의 소트 부하 경감 원리
Top 10 (rownum <= 10)이면, 10개의 레코드를 담을 배열만 할당(소트 영역 사용량 줄임)해서
처음 10개 레코드를 정렬된 상태로 담음.
이후 읽는 레코드에 대해서 맨 우측의 가장 큰값과 비교해서,
그보다 작은 값이 나타날 때만 배열 내에서 다시 정렬 시도.(소트 연산 횟수 줄임)
[] Top-N 쿼리 알고리즘이 작동하지 못하는 경우
SELECT *
FROM (SELECT ROWNUM NO
, 거래일시
, 체결건수
, 체결수량
, 거래대금
FROM (SELECT 거래일시
, 체결건수
, 체결수량
, 거래대금
FROM 시간별종목거래
WHERE 종목코드 = :isu_cd
AND 거래일시 >= :trd_time
ORDER BY 거래일시
)
WHERE ROWNUM <= 100 ---------------- 빼면 Top-N 쿼리 미작동
)
WHERE NO BETWEEN 91 AND 100
-- 조건절에 ROWNUM 이 있어야 작동하는 듯 함.
[] 윈도우 함수에서의 Top-N 쿼리
select 고객ID, 변경순번, 전화번호, 주소, 자녀수, 직업, 고객등급
from (select 고객ID, 변경순번
, max(변경순번) over(partition by 고객ID) 마지막변경순번
, 전화번호, 주소, 자녀수, 직업, 고객등급
from 고객변경이력)
where 변경순번 = 마지막변경순번
-- max() 함수보다 rank()나 row_number() 함수를 사용하는 것이 Top-N 쿼리 알고리즘이 작동하므로 유리함.
select 고객ID, 변경순번, 전화번호, 주소, 자녀수, 직업, 고객등급
from (select 고객ID, 변경순번
, rank(변경순번) over(partition by 고객ID order by 변경순번 desc) rnum
, 전화번호, 주소, 자녀수, 직업, 고객등급
from 고객변경이력)
where rnum = 1
alter session set sort_area_size = 1048576;
alter session set workarea_size_policy = manual;
alter session set sort_area_size = '10485760;