본문 바로가기

데이터베이스/SQL 튜닝

[SQL튜닝/고급SQL튜닝] 소트 튜닝1 (메모리 소트와 디스크 소트, 소트 발생 오퍼레이션, JO GA WU)

🖥️ 들어가며

📌 소트를 발생시키는 오퍼레이션: JO GA UW
- Sort Join
- Sort Order by
- Sort Group by
- Sort Aggregate
- Sort Unique
- Window Sort

 


 

✏️ 1. 메모리 소트와 디스크 소트

구분 설명
메모리 소트 (In-Memeory) ⭐⭐⭐ 전체 데이터의 정렬 작업을 → 할당받은 메모리 내에서 완료하는 것을 말합니다.
Internal Sort 또는 Optimal Sort라고도 합니다.
디스크 소트 (To-Disk) ⭐⭐⭐ 할당받은 소트 영역 내에서 정렬을 완료하지 못해, 디스크 공간까지 사용하는 것을 말합니다.
External Sort라고도 합니다.
- Onepass Sort: 정렬대상집합을 디스크에 1번만 기록합니다.
- Multipass Sort: 정렬대상집합을 디스크에 여러 번 기록합니다.
  • Oracle: 소트 영역을 PGA에 할당하고, SQL Server: 버퍼 캐시에 할당합니다.
  • 소트 시 메모리 공간이 부족할 경우 디스크 공간을 사용합니다.
    • SORT RUNS: 정렬된 중간결과집합을 임시로 저장하는 Temp 영역의 집합입니다.
      [발생하는 경우]
      - Sort Area 내에서 데이터 정렬을 마무리할 수 없을 때
      - Sort할 데이터의 양이 많을 때
    • SORT_AREA_SIZE
      - 8버전: DBA가 직접 설정 필요 (HASH_AREA_SIZE)
      - 9버전 이후: WORKAREA_SIZE_POLICY = Auto; DBMS가 자동 지정함

 

👉🏻 PGA vs. UGA

📌 전용서버 방식(Dedicated Server)
- 각 클라이언트 요청에 대해 하나의 서버 프로세스가 전용으로 할당됩니다.
- 하나의 사용자 세션이 하나의 서버 프로세스를 독점적으로 사용합니다. (1:1)

📌 공유서버 방식(Shared Server)
- 여러 클라이언트의 요청이 공유서버프로세스(Shared pool)에 의해 처리됩니다.
- 클라이언트가 서버 프로세스를 공유합니다.
  • 전용서버 방식일 경우
    • PGA 내에는 CGA, UGA가 포함됩니다.
    • CGA: 개별 Call인 진행되는 동안만 임시 작업 데이터 저장.
      (개별 Call의 예시: SQL 실행)
    • UGA: 세션 상태 데이터를 저장, 현재 Call을 넘어 다음 Call까지 지속적으로 참조.
      (세션 상태 데이터의 예시: 세션 변수, 커서 상태 등)
구분 PGA UGA
영역 Process Global Area User Global Area
설명 하나의 프로세스 또는 백그라운드 프로세스를 위한 메모리 영역 사용자 세션과 관련된 메모리 영역
위치 프로세스 메모리 공간에 위치(항상 프로세스에 종속적) 서버 구성에 따라 UGA가 PGA 또는 SGA에 위치
- 전용서버 방식(Dedicate Server): PGA 내
- 공유서버 방식(Shared Server): SGA 내
공유여부 비공유 (프로세스 전용) - 전용서버 방식(Dedicate Server): 비공유 
- 공유서버 방식(Shared Server): 공유
메모리 할당과 수명 - 프로세스 시작 시 할당
- 프로세스 종료 시까지
- 세션 시작 시 할당
- 세션 종료 시 까지
하나의 기준 하나의 프로세스는 하나의 PGA를 갖음 하나의 세션은 하나의 UGA를 갖음
정보 관리 세션과 독립적인 프로세스만의 정보 관리 즉, 작업 데이터 관리
(정렬, 해시 조인 등)
프로세스와 독립적인 세션만의 정보 관리 즉, 세션 상태 데이터 관리
(세션 변수, 커서 상태, 패키지 상태 등)

 

 

 

✏️ 2. 소트를 발생시키는 오퍼레이션

  • ⭐⭐⭐ JO GA WU
  • Sort Aggregate
    • 전체 로우를 대상으로 집계를 수행할 때 나타납니다.
    • SORT라는 표현을 사용하지만 실제 소트가 발생하진 않습니다.
select sum(sal), max(sal), min(sal) from emp;

--------------------------------------------
| Id  | Operation          | Name | E-Rows |
--------------------------------------------
|   0 | SELECT STATEMENT   |      |        |
|   1 |  SORT AGGREGATE    |      |      1 |
|   2 |   TABLE ACCESS FULL| EMP  |   7013 |
--------------------------------------------

 

  • Sort Order By
    • 데이터 정렬을 위해 Order By 오퍼레이션을 수행할 때 나타납니다.
select * from emp order by sal desc;

-----------------------------------------------------------------------
| Id  | Operation          | Name | E-Rows |  OMem |  1Mem | Used-Mem |
-----------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |        |       |       |          |
|   1 |  SORT ORDER BY     |      |   7013 | 14336 | 14336 |12288  (0)|
|   2 |   TABLE ACCESS FULL| EMP  |   7013 |       |       |          |
-----------------------------------------------------------------------

 

  • Sort Group By
    • 소팅 알고리즘을 사용해 그룹별 집계를 수행할 때 나타납니다.
    • ⭐⭐⭐ 정렬된 Group By 결과를 얻고자 한다면 "반드시 Order By"를 명시해야 합니다.
    • ⭐⭐⭐ 실행계획에 Sort Group By 표시 = 소팅 알고리즘을 사용해 값을 집계한다 != 최종결과의 정렬
    • Hash Group By
      • 10gR2버전부터 Hash Group By 방식이 도입되었습니다.
      • Order By절을 함께 명시하지 않으면 대부분 Hash Group By로 처리됩니다.
      • ⭐⭐⭐ Hash Group By는 정렬을 수행하지 않고, 해싱 알고리즘을 사용해 데이터를 그룹핑합니다.
        • 즉, 읽는 로우마다 Group By 컬럼의 해시 값으로 버킷을 찾아 그룹별로 집계항목을 갱신합니다.
    • 집계할 대상의 로우가 많아도, 그룹 개수가 소수일 때는 두 방식 모두 Disk Sort가 전혀 발생하지 않습니다. (PGA 연산)
      • 만약 직원테이블(EMP)에 100만 명의 직원 정보(EMP_ID), 각 직원은 10개 부서(DEPT_ID) 중 하나에 속해 있음
      • 이 상태에서 각 부서별 평균급여를 계산하려면? (= 10개 그룹)
        SELECT      DEPT_ID, AVG(SALARY)
        FROM         EMP
        GROUP BY DEPT_ID;
      • 여기서 중요한 점은 그룹의 수(=부서의 수)가 적다는 것입니다.
        (전체 로우는 100만 개지만, 실제 메모리에 유지해야 하는 그룹은 10개뿐)
        • SORT GROUP BY
          : 정렬 시 100만 개 로우를 모두 메모리에 올릴 필요 없이, 각 부서별로 현재까지의 합계와 처리한 로우의 수만 유지하면 됨 (즉, 10개의 부서에 대한 정보만 메모리에 유지하면 됩니다.)
        • HASH GROUP BY
          : 10개의 해시 버켓만 메모리에 유지하면 됩니다.
-- ☆ 정렬된 Group By 결과를 얻으려면 반드시 ORDER BY 필요 ☆
-- SORT GROUP BY: 소팅 알고리즘 -> 그룹별 집계 수행
select deptno, job, sum(sal), max(sal), min(sal)
from emp
group by deptno, job
order by deptno, job;

-----------------------------------------------------------------------
| Id  | Operation          | Name | E-Rows |  OMem |  1Mem | Used-Mem |
-----------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |        |       |       |          |
|   1 |  SORT GROUP BY     |      |      4 |  3072 |  3072 | 2048  (0)| -- SORT GROUP BY
|   2 |   TABLE ACCESS FULL| EMP  |   7013 |       |       |          |
-----------------------------------------------------------------------

-- HASH GROUP BY: 해싱 알고리즘 -> 그룹별 집계 수행
select deptno, job, sum(sal), max(sal), min(sal)
from emp
group by deptno, job

-----------------------------------------------------------------------
| Id  | Operation          | Name | E-Rows |  OMem |  1Mem | Used-Mem |
-----------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |        |       |       |          |
|   1 |  HASH GROUP BY     |      |      4 |   780K|   780K|  481K (0)| -- HASH GROUP BY
|   2 |   TABLE ACCESS FULL| EMP  |   7013 |       |       |          |
-----------------------------------------------------------------------

 

  • Sort Unique
    • 주요 목적
      • 중복 레코드를 제거하는 것입니다.
      • 이는 메인쿼리와 서브쿼리의 조인 결과에서 중복을 없애기 위해 수행됩니다.
    • Sort Unique 실행 케이스
      • Case 1. Unnesting된 서브쿼리가 M쪽 집합인 경우
        • SELECT *
          FROM    EMP
          WHERE DEPT_ID IN (SELECT DEPT_ID FROM DEPT WHERE LOC_ID = 1700);
        • 여기서 서브쿼리는 여러 개의 DEPT_ID를 반환할 수 있습니다.
        • 이 경우 Oracle은 서브쿼리를 Unnesting하여 메인쿼리와 조인을 시도합니다.
        • 서브쿼리가 M쪽 집합이고 드라이빙한다면 Oracle은 중복을 제거하기 위해 Sort Unique 오퍼레이션을 수행합니다. (1쪽 드라이빙: 세미 조인)
      • Case 2. Unique 인덱스가 없는 경우
        • 만약 조인컬럼(위 경우 DEPT_ID)에 Unique 인덱스가 없다면, Oracle은 중복을 제거하기 위해 Sort Unique을 수행합니다.
        • UNION: 중복 제거
        • DISTINCT: 중복 제거
      • Case 3. 세미 조인으로 수행되지 않는 경우
        • 세미 조인은 주로 EXISTS나 IN 조건을 사용할 때 발생합니다.
        • SELECT DISTINCT d.deptno, d.dname
          FROM     dept d
          WHERE  d.deptno IN (SELECT e.deptno FROM emp e WHERE e.sal > 2000)
          ORDER BY d.deptno;
          • 보통 IN절이 일반적으로 세미조인의 후보가 되지만, 위 SQL은 DISTINCT와 ORDER BY절이 함께 사용되었습니다.
          • 이 경우, 중복 제거(DISTINCT)가 필요하고 + 결과를 정렬해야 하고(ORDER BY) + IN 서브쿼리의 결과가 중복될 가능성이 있습니다.
          • Sort Unique 연산은 중복 제거 + 정렬을 수행 => 두 가지 목적을 동시에 달성합니다.
      • 예외: Sort Unique를 사용하지 않고 Hash Unique를 사용하는 경우
        • Oracle 10gR2부터는 Distinct 연산에서도 Order By를 생략하면 => Hash Unique 방식으로 수행됩니다.
        • "_convert_set_to_join" = true로 설정하면 집합 연산에서 => Hash Unique 방식으로 수행됩니다.
-- 1. Unnesting된 서브쿼리가 M쪽 집합인 경우
select /*+ ordered use_nl(dept) */ *
from dept
where deptno in (select /*+ unnest */ deptno
                   from emp where job = 'CLERK');
------------------------------------------------------------------------------------
| Id  | Operation                    | Name    | E-Rows |  OMem |  1Mem | Used-Mem |
------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |         |        |       |       |          |
|   1 |  NESTED LOOPS                |         |        |       |       |          |
|   2 |   NESTED LOOPS               |         |      1 |       |       |          |
|   3 |    SORT UNIQUE               |         |   6996 |  3072 |  3072 | 2048  (0)|
|*  4 |     TABLE ACCESS FULL        | EMP     |   6996 |       |       |          |
|*  5 |    INDEX UNIQUE SCAN         | DEPT_PK |      1 |       |       |          |
|   6 |   TABLE ACCESS BY INDEX ROWID| DEPT    |      1 |       |       |          |
------------------------------------------------------------------------------------

-- 2. Unique 인덱스가 없는 경우
-- UNION 연산
select job, mgr from emp where deptno = 10
union
select job, mgr from emp where deptno = 20;
--------------------------------------------------------------------------------------------
| Id  | Operation                     | Name           | E-Rows |  OMem |  1Mem | Used-Mem |
--------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |                |        |       |       |          |
|   1 |  SORT UNIQUE                  |                |   7014 |  3072 |  3072 | 2048  (0)|
|   2 |   UNION-ALL                   |                |        |       |       |          |
|*  3 |    TABLE ACCESS FULL          | EMP            |   7013 |       |       |          |
|   4 |    TABLE ACCESS BY INDEX ROWID| EMP            |      1 |       |       |          |
|*  5 |     INDEX RANGE SCAN          | EMP_DEPTNO_IDX |      1 |       |       |          |
--------------------------------------------------------------------------------------------

-- DISTINCT 연산
select distinct deptno from emp order by deptno;
-----------------------------------------------------------------------
| Id  | Operation          | Name | E-Rows |  OMem |  1Mem | Used-Mem |
-----------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |        |       |       |          |
|   1 |  SORT UNIQUE       |      |      1 |  3072 |  3072 | 2048  (0)|
|   2 |   TABLE ACCESS FULL| EMP  |   7013 |       |       |          |
-----------------------------------------------------------------------

-- 예외. HASH UNIQUE 사용하는 경우
-- 1. DISTINCT에서 ORDER BY절 생략
select distinct deptno from emp;
-----------------------------------------------------------------------
| Id  | Operation          | Name | E-Rows |  OMem |  1Mem | Used-Mem |
-----------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |        |       |       |          |
|   1 |  HASH UNIQUE       |      |      1 |  1518K|  1518K|  286K (0)|
|   2 |   TABLE ACCESS FULL| EMP  |   7013 |       |       |          |
-----------------------------------------------------------------------

-- 2. "_conver_set_to_join" = true
-- 집합 연산: union, union all, minus, intersect
alter session set "_convert_set_to_join" = true;

select job, mgr from emp where deptno = 10
minus
select job, mgr from emp where deptno = 20;
--------------------------------------------------------------------------------------------
| Id  | Operation                     | Name           | E-Rows |  OMem |  1Mem | Used-Mem |
--------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |                |        |       |       |          |
|   1 |  HASH UNIQUE                  |                |     22 |  1115K|  1115K|  550K (0)|
|*  2 |   HASH JOIN RIGHT ANTI        |                |   5610 |  1594K|  1594K|83968  (0)|
|   3 |    TABLE ACCESS BY INDEX ROWID| EMP            |      1 |       |       |          |
|*  4 |     INDEX RANGE SCAN          | EMP_DEPTNO_IDX |      1 |       |       |          |
|*  5 |    TABLE ACCESS FULL          | EMP            |   7013 |       |       |          |
--------------------------------------------------------------------------------------------

 

  • Sort Join
    • 소트 머지 조인을 수행할 때 나타납니다. (use_merge)
select /*+ordered use_merge(e) */*
from dept d, emp e
where d.deptno = e.deptno;

------------------------------------------------------------------------------------
| Id  | Operation                    | Name    | E-Rows |  OMem |  1Mem | Used-Mem |
------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |         |        |       |       |          |
|   1 |  MERGE JOIN                  |         |   7013 |       |       |          |
|   2 |   TABLE ACCESS BY INDEX ROWID| DEPT    |      4 |       |       |          |
|   3 |    INDEX FULL SCAN           | DEPT_PK |      4 |       |       |          |
|*  4 |   SORT JOIN                  |         |   7013 | 14336 | 14336 |12288  (0)|
|   5 |    TABLE ACCESS FULL         | EMP     |   7013 |       |       |          |
------------------------------------------------------------------------------------
-- DEPT 테이블의 인덱스를 사용하였기 때문에(즉, 이미 정렬된 상태이므로)
-- EMP 테이블만 SORT JOIN 연산 발생

 

  • Window Sort
    • 분석함수를 수행할 때 나타납니다.
-- 분석함수
SELECT  분석함수(컬럼명)
OVER    ([PARTITION BY 그룹 지을 컬럼 LIST]
         [ORDER BY 정렬할 컬럼 LIST]
         [WINDOWING 절 (Rows|Range|Unbounded Preceding|Unbounded Following|Current Row)]
         )
FROM     테이블명

-- WINDOW SORT
select empno, ename, job, mgr, sal
     , avg(sal) over (partition by deptno)
from emp;
-----------------------------------------------------------------------
| Id  | Operation          | Name | E-Rows |  OMem |  1Mem | Used-Mem |
-----------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |        |       |       |          |
|   1 |  WINDOW SORT       |      |    114 | 11264 | 11264 |10240  (0)|
|   2 |   TABLE ACCESS FULL| EMP  |    114 |       |       |          |
-----------------------------------------------------------------------

 

 

 

✏️ 3. 소트 튜닝 요약

  • 소트 오퍼레이션
    • 메모리 집약적, CPU 집약적, 데이터량이 많을 때는 디스크 I/O까지 발생 시킵니다.
    • 특히, 부분범위처리를 할 수 없게 만듭니다.
    • 될 수 있으면 소트가 발생하지 않도록 쿼리를 작성해야 하고, 불가피하다면 메모리 내에서 수행하도록 해야 합니다.
  • 소트 튜닝2에서 다룰 내용
    • 소트가 발생하지 않는 SQL 작성, 인덱스를 이용한 소트 연산 대체, 소트 영역을 적게 사용하는 SQL 작성, 소트 영역 크기 조정

📒 정리하면

⭐⭐⭐ JO GA WU

  1. 메모리 소트: 할당된 메모리 내에서 정렬 완료
    디스크 소트: 디스크 공간까지 사용합니다.
  2. Sort Arregate: 전체 로우를 대상으로 집계할 때 나타납니다. (실제 소트 발생X)
  3. Sort Order By: 데이터 정렬을 위해 Order By 오퍼레이션 수행 시 나타납니다.
  4. Sort Group By: 소팅 알고리즘으로 그룹별 집계를 수행합니다.
    1. Hash Group By
      : Oracle 10gR2부터, 해싱 알고리즘을 사용합니다.
    2. 그룹 개수
      : 적을 경우, Sort Group By와 Hash Group By 모두 PGA 내에서 연산을 완료할 수 있습니다. (디스크 소트 X)
  5. Sort Unique: 중복 레코드 제거를 위해 사용
    1. Sort Unique 발생 케이스
      : Unnesting된 서브쿼리가 M쪽 집합인 경우, Unique 인덱스가 없는 경우, 세미 조인으로 수행되지 않을 경우
    2. Hash Unique (ORacle 10gR2부터)
      : Distict 연산에서 Order By를 생략하는 경우, "_convert_set_to_join" = true로 설정 후 집합 연산을 하는 경우
  6. Sort Join: 소트 머지 조인 수행 시, 조인 키로 정렬된 테이블에는 Sort Join이 발생하지 않습니다. (인덱스로 이미 기정렬)
  7. Window Sort: 분석 함수 수행 시 나타납니다.

반응형