본문 바로가기
IT/DB

DB 튜닝을 알아보자

by 모띠 2022. 1. 24.

실행계획이란?

사용자가 SQL을 실행하여 데이터를 추출하려고할 때, 옵티마이저가 수립하는 작업절차(SQL의 성능에 굉장히 중요)

SQL해석 → 실행계획수립 → 실행

[왼쪽 아래가 제일 먼저실행]

실행계획 확인방법

  1. EXPLAIN PLAN
    1. 실행계획을 떠서 저장해놓고 확인하는 방식
    2. SQL에 대한 실행계획만을 확인할 수 있음
    3. 명령을 사용할때 데이터를 실제로 처리하지않음 (DB에 부하를 주지않기때문에 유용함)
    4. 다만 SQL문이 여러개면 SQL을 바꿔가면서 작성해야하는 불편함 존재
    5. 실제로 확인하기위해서는 다시 SELECT를 해야하는 불편함 존재
    6. 실제로 DB실행이 된것도 아니기때문에 소요시간도 체크 불가능
  2. SET AUTOTRACE
    1. EXPLAIN PLAN 명령과는 달리 한번의 명령으로, 여러개의 SQL에 대한 실행계획을 바로 볼수있음
    2. 다양한 옵션을 사용할수있어서 여러가지의 정보를 선택적으로 확인할수있음 (실제로 실행안되게 한다던지..)
    SET AUTOTRACE ON;
    SELECT /*+ USE_NL(e d) */
    			e.ename, e.deptno, d.dname
    FROM emp e, dept d
    WHERE e.deptno = d.deptno;
    
    • 명령어 옵션
    1. SET AUTOTRACE ON; 실행시 출력결과를 화면에 나타내고, 그밑에 실행계획 나타내고, 그아래에 IO & 소팅관련 정보 제공
    2. 이것만쓰면 전체적으로 다 나오기때문에 보고싶은정보만 추가적으로 선택하는것
    3. SET AUTOTRACE ON EXPLAIN; 실행시 출력결과를 화면에 나타내고, 그밑에 실행계획까지만 나타낸다
    4. SET AUTOTRACE ON STATISTICS; 실행시 출력결과를 화면에 나타내고, 그밑에 IO관련 정보를 나타낸다
    5. SET AUTOTRACE TRACEONLY; 출력결과를 보이고싶지않을때 사용(몇건만됐다는것만 나옴), 그 밑에 IO정보들이 나옴
    6. SET AUTOTRACE TRACEONLY EXPLAIN; 출력결과를 보여주지않고 그밑에 실행계획만 추출 (데이터를 실제로 안돌림)
    7. SET AUTOTRACE TRACEONLY STATISTICS; 출력결과를 보여주지않고 그밑에 IO만 추출(데이터 읽어야 IO가 나옴)
    8. SET AUTOTRACE OFF; 정보를 활용하고싶지않을때

옵티마이저란

사용자가 실행한 SQL을 해석하고, 데이터 추출을 위한 실행계획을 수립하는 프로세스

오라클은 옵티마이저가 2종류가 있음

  • RBO - 초장기 버전부터 제공 (기본적으로 15개의 순위가 매겨진 규칙이 있어서 그걸 기준으로 실행계획 수립)
  • CBO - 10g부터 기본적으로 제공CBO 성능을 최적상태로 유지시키기위해 테이블, 인덱스, 클러스터 등을 대상으로 통계정보를 생성함 → 정기적으로 ANALYZE 작업을 해야함
    1. ANALYZE TABLE emp COMPUTE STATISTIC; 테이블이 가지고 있는 전체 데이터를 대상으로 할때 사용
    2. ANALYZE TABLE emp ESTIMATE STATISTIC; 데이터가 너무 많을때는 표본크기로
    3. ANALYZE TABLE emp SAMPLE 10 PERCENT;
    4. ANALYZE TABLE emp ESTIMATE STATISTIC;
    5. ANALYZE TABLE emp SAMPLE 5 ROW;
    ANALYZE가 실행되었는지는 아래와 같은 쿼리로도 확인할수있고 (테이블이름만 나오면 ANALYZE된적없다는뜻) 
SELECT table_name, num_rows, last_analyzed
FROM user_tables
WHERE table_name IN ('EMP', 'DEPT');

오라클같은경우는 package를 통해서도 할수있다.

package를 사용하면 명령어를 사용해서 할때보다 편하게할수있고 oracle 10g이상부터는 작업이 자동화되어있다. (사실상 할필요없다는 뜻인거같음)


옵티마이저 레벨별 설정

  1. instance level : initSID.ora를 이용하여 지정함 OPTIMIZER_MODE = [RULE/CHOOSE/FIRST_ROWS/ALL_ROWS]
  2. 디비 전체차원에서 설정, 10g부터는 ALL_ROWS가 기본설정. 우선순위 제일 낮음
  3. Session level : ALTER SESSION SET OPTIMIZER_MODE = [RULE/CHOOSE/FIRST_ROWS/ALL_ROWS]
  4. 작업하는 세션에 한에서만 설정
  5. Statement level : SELECT /+first_rows/ ename FROM emp;
  6. SQL문에다가 힌트로 따로 지정해줄수있음. 우선순위 제일 높음
  • 옵티마이저레벨별 설정은 3가지가 있다.
  • package를 사용하면 명령어를 사용해서 할때보다 편하게할수있고 oracle 10g이상부터는 작업이 자동화되어있다. (사실상 할필요없다는 뜻인거같음)
  • SELECT table_name, num_rows, last_analyzed FROM user_tables WHERE table_name IN ('EMP', 'DEPT');
  • ANALYZE 실행
  • 대상 row들을 처리하는데 필요한 자원사용을 최소화해서, 궁극적으로 데이터를 빨리처리하려고 알아서 최적화 계산을함

오라클을 제외한 나머지는 CBO

RBO와 CBO는 실행계획이 다름.

RBO는 룰이따라서 WHERE절에 상수가 있는 테이블을 먼저 조회함


스캔종류

  1. full table scan : 테이블의 전체 테이터를 읽어 조건에 맞는 데이터를 추출하는 방식
  2. rowid scan : rowid를 기준으로 데이터를 추출하는 방식으로 단일행에 접근하는 방식 가운데에서 가장 빠름
  3. index scan : 원하는 데이터를 추출하기 위해 인덱스를 사용하여 검색하는 방식

full scan이 index scan보다 반드시 느린건 아니다.


인덱스

인덱스를 사용해야하는 이유

  • 데이터베이스에 저장된 자료를 더욱빠르게 조회하기 위해 인덱스를 생성하여 사용
  • 일반적으로, 인덱스는 테이블의 전체 데이터중 10~15% 이하의 데이터를 처리하는 경우에 효율적이며, 그 이상은 안쓰는게 더 나음
  • 기본적으로 B tree구조이므로 소팅이 되어있기때문에 ORDER BY를 안써도됨
  • MAX /MIN 을 구하기 쉬움

인덱스는 B Tree구조로 저장됨 (루트 / 브랜치 / 리프 노드로 구성)

데이터는 리프에만 있으며 리프는 소팅이 되어있음 (인덱스를 쓰게되면 ORDER BY를 안써도됨)

// (A,B,C) 로 구성된 인덱스가 있을때 아래는 INDEX RANGE SCAN DESCENDING가 됨.
// 내림차순으로 아예 접근하므로 ORDER BY를 안해도됨 (성능에는 큰차이)
// ORDER BY로 묶여있는 C를 인덱스로 잡아놨기때문

SELECT A, B, C
FROM TT
WHERE A = 160
AND B = '2002'
ORDER BY C DESC;
// (A,B,C) 로 구성된 인덱스가 있을때 아래는 INDEX RANGE SCAN (MIN / MAX)가 됨.
// 인덱스를 타기때문에 바로 가져올수있음
// 인덱스를 구성안하면 조건에 대한 데이터를 다 찾고 소팅을 통해서 찾아나가야함

SELECT MAX(C)
FROM TT
WHERE A = 160
AND B = '2002'

인덱스 선정 절차

  1. 프로그램 개발에 이용된 모든 테이블에 대하여 access path조사
  2. 인덱스 컬럼 선정 및 분포도 조사
  3. 자주사용되는 critical access path결정 및 우선 순위 선정
  4. 인덱스 칼럼의 조합 및 순서 결정 (각각은 안좋아도 결합하면 좋은 성능의 인덱스가 있을 수 있으니까 체크하라는말)
  5. 시험 생성 및 테스트 (성능이 좋을것으로 예상되었지만 실제로 안 좋은 인덱스를 걸러내라)
  6. 결정된 인덱스 기준으로 프로그램 반영
  7. 적용

주의점

  • 기존 sql에 어떤 영향이 있는지 검토를 해야함
  • 인덱스 과다 생성으로 인한 dml 작업 속도 (select만 빨라지고 cud는 느려짐)
  • 비록 개별 칼럼 분포도가 좋지 않더라도 다른칼럼과 결합하여 자주 사용되고, 갈협한경우에 분포도가 양호하다면 결합 인덱스 생성을 긍정적으로 검토

인덱스 스캔원리

  1. 조건을 만족하는 최초의 인덱스 row를 찾음
  2. access된 인덱스 row의 rowid를 이용해서 테이블에 있는 row를 찾음(랜덤액세스)
  3. 처리 범위가 끝날때까지 차례대로 다음인덱스 row를 찾으면서 2를 반복
  • 인덱스 스캔시에는 한번의 i/o가 발생할때마다 한개의 block씩 처리

인덱스 사용 (어떻게하면 인덱스를 사용하게 할수있을까?)

1. 고유(unique) 인덱스의 ' = ' 검색

// empno가 pk임
SELECT * FROM emp WHERE empno = 1234;

2. 고유(unique) 인덱스의 범위 검색

SELECT * FROM emp WHERE empno >= 1234;

3. 중복(non-unique) 인덱스의 범위 검색

CREATE INDEX job_index ON emp (job);

SELECT * FROM emp WHERE job = 1234; // job은 pk가 아니므로 여러건이 들어갈수있으므로 범위검색에 들어감
SELECT * FROM emp WHERE job like '%1234';

4. OR & IN 조건 - 결과의 결합 (OR & IN 은 내부적으로 = 연산 수행)

SELECT * FROM emp WHERE empno IN (1234,235);
SELECT * FROM emp WHERE empno = 1234 OR empno = 235;

5. NOT BETWEEN

SELECT * FROM emp WHERE empno NOT BETWEEN 1234 AND 235; == 사실상 OR임


결합인덱스

여러개의 컬럼을 인덱스로 만듬, 첫번째가 인덱스로 구성되어있는게 성능에 좋다

  • 결합인덱스 컬럼선택 기준
    1. where절에서 and조건으로 자주결합되어 사용되면서 각각의 분포도 보다 두 개 이상의 컬럼이 결합될때 분포도가 좋아지는 컬럼들
    2. 다른 테이블과 조인의 연결고리로 자주사용되는 컬럼들
    3. 하나 이상의 키 칼럼 조건으로 같은 테이블의 컬럼들이 자주조회될때, 이러한 컬럼을 모두 포함
  • 결합인덱스의 컬럼 순서결정(순서가 굉장히 중요함) - 첫번째를 만족하는 컬럼을 찾고 그중에서 두번째기준을 찾으므로 첫번째부터 범위를 줄이는것이 중요
    1. where절 조건에 많이 사용되는 칼럼 우선
    2. =로 사용되는 칼럼 우선
    3. 분포도가 좋은 칼럼 우선(범위가 좁은것)
    4. 자주 이용되는 sort의 순서로 결정

결합인덱스 사용가능 예

1. 인덱스 : (aa, bb, cc)

// 이런식으로 인덱스의 첫번째인 aa컬럼이 항상 where절에 포함되어있을때 사용가능

WHERE aa = '1234';
WHERE aa = '1234' and bb = 'shin';
WHERE aa = '1234' and bb = 'shin' and cc = 't12';

2. index skip scanning

결합인덱스의 첫번째 컬럼이 where에 없으면 원래는 결합인덱스를 사용하지않는데, 두번째 컬럼부터 where에 조건으로 기술되어있으면 사용가능

다만 강제로 index skip scanning을 타도록 힌트가 존재함

// 이 힌트를 사용하면 index skip scanning을 탐
INDEX_SS (테이블명 인덱스명)
INDEX_SS_ASC (테이블명 인덱스명)
INDEX_SS_DESC (테이블명 인덱스명)

결합인덱스 = 의미

=이 범위제한 조건을 가지면 속도를 낼수있고, 체크조건이라면 속도를 내지못한다.

인덱스 : (시, 구, 동)

WHERE 시 LIKE '서%'
AND 구 = '강남구' <- 체크조건 (범위를 제한하지 못함)
AND 동 = '역삼동'; <- 체크조건

// 서울시까지는 잘찾는데 역삼동은 바로 찾아내지못함(앞의 조건이 애매하기때문)
WHERE 시 = '서울시' <- 범위제한조건
AND 구 LIKE '강%'
AND 동 = '역삼동'; <- 체크조건

// 앞과 동일하게 서울시까지는 찾지만 앞의조건이 애매해서 역삼동은 바로 찾지못함
WHERE 시 = '서울시' <- 범위제한조건
AND 동 = '역삼동'; <- 체크조건

// 이렇게 짜는것이 성능에 가장좋음
WHERE 시 = '서울시' <- 범위제한조건
AND 구 = '강남구' <- 범위제한조건
AND 동 = '역삼동';  <- 범위제한조건

인덱스 매칭률 향상을 통한 속도개선

매칭률을 높여야함 → where절에서 첫번째 컬럼부터 연속된 칼럼에 대해 상수값을 = 로 비교하는 칼럼의갯수 / 인덱스를 구성하는 칼럼의 총 갯수

인덱스 : (급여연월, 급여코드 사원번호)

// 매칭률 0/3
WHERE 급여연월 LIKE '2016%' // LIKE절이기때문에 범위를 제한하지못함
AND 급여코드 = '정기급여'; // 윗줄이 애매하므로 아랫줄도 범위를 제한하지못함 

튜닝후

// 매칭률 2/3
WHERE 급여연월 IN ('201601', '201602', '201603'....) // IN은 내부적으로 = 연산이므로 범위제한
ANS 급여코드 = '정기급여'; // 윗줄이 범위제한이므로 =도 범위제한

인덱스 스캔을 하면 무조건 빠른가?

조건에 의한 처리범위가 넓어짐으로 인해 분포도가 나빠지는 경우가 있는데, 이경우는 full scan하는게 훨씬 나음 (15%이상의 데이터를 찾을때는 안좋음)

인덱스는 한번의 i/o 때마다 1개의 블락만 처리하기때문에 그렇다. (full scan은 한번 i/o때 여러개의 블락을 처리함)

  • 인덱스를 못쓰는 경우
  1. NOT 연산자 (1개를 제외하고 모두니까 과반수를 찾는것이므로)
  2. IS NULL, IS NOT NULL (인덱스는 NULL을 못집어넣음)
  3. 옵티마이저의 취사선택 (옵티마이저가 자의적으로 잘못된선택을 할수있음 - Hint로 강제 제어 가능)
  4. External suppressing (외부적으로 가해지는 suppressing (인덱스로 구성된 컬럼에 변형을 가했을때 사용하지못하는것을 의미))
WHERE SUBSTR(ename,1,1) = 'M'; // ename이 인덱스라도 변형이 가해졌기때문에 사용불가
WHERE job||deptno = 'MANAGER10'; // 문자열결합도 사용불가
WHERE TO_CHAR(hiredate, 'YYYYMMDD') = '20021016'; // DATE변수의 가공도 인덱스사용불가
WHERE hiredate BETWEEN TO_DATE('20021016', 'YYYYMMDD') AND TO_DATE('20021016', 'YYYYMMDD'); // 이렇게 수정해야함
WHERE sal * 12 > 40000; // 이것도 컬럼에 변화가 가해지므로 불가능
WHERE sal > 40000/12 // 이런식으로 바꿔야함 
  1. Internal suppressing(데이터베이스 내부에 스스로 존재하는 suppressing, 예를들어 varhchar2와 number를 비교했을떄 한쪽이 디비내부적으로 변형됨)
// 이런것들이 자동형변환
comm + '500' // 500이 숫자로 자동형변환되서 가능
bonus > sal / '10' // 10이 숫자로 자동형변환
MOD (sal, '100') // 100이 숫자로 자동형변환
WHERE hiredate = '2003-01-01'; // hiredate는 DATA타입인데 문자로 입력받음. 알아서 DATA로 바꿔줌. 여긴 컬럼에 변화가 없으므로 인덱스사용 가능
WHERE ROWID = 'AAAada'; // ROWID는 타입이 별도로 존재하는데 문자로 입력받음. 알아서 ROWID타입으로 변경됨. 여기도 인덱스사용가능

WHERE TO_NUMBER(resno) = 12314; // reson는 varchar2타입인데 TO_NUMBER함수로 변형함. 인덱스사용불가 FULL SCAN함

특정테이블에 대해 사용할수있는 인덱스가 2개이상일때

  • 옵티마이저가 조건에 가장 적절한 인덱스를 선택해서 사용함
  • 주어진 조건에 가장 적절한 인덱스를 선택하려할때 일련의 절차에 따라 결정
// 인덱스1번 : 부서 + 기준일자 (선택) 매칭률 2/2, 개수2개
// 인덱스2번 : 품목                  매칭률 1/1, 개수1개

SELECT *
FROM SALES
WHERE 부서 = '123'
AND 기준일자 = '201201'
AND 품목 = 'B023';

//  인덱스 변경 //////
// 인덱스 1번 : 부서 + 기준일자 + 순번 매칭률 2/3
// 인덱스 2번 : 품목 (선택)            매칭률 1/1
  • 옵티마이저의 인덱스 선택 시 판단 절차
  1. 주어진 조건에 대한 인덱스별로 매칭률을 계산후, 매칭률이 높은것을 우선 선택
  2. 인덱스 별 매칭률이 같을때 인덱스를 구성하는 칼럼의 개수가 많을 것을 우선 선택
  3. 인덱스 매칭률 & 칼럼개수가 전부 같은경우 가장 최근에 생성된 것을 우선 선택

근데 RBO CBO가 선택방식이 조금 다른게, 매칭률이 낮더라도 더 좁은 범위를 쫍힐수있는 인덱스라면 CBO는 그걸 선택함

기본적으로 그룹바이를 먼저적용시켜서 범위를 줄이는게 더 도움이 될꺼임(서브쿼리로 그룹바이를 먼저실행하면됨)



NESTED LOOPS 조인

  • 드라이빙 테이블 (outer 테이블) / 드라이븐 테이블 (inner 테이블)로 구성
  • 드라이빙 테이블의 각 row에 대해 이들이 추출될때마다 드라이븐 테이블의 연관된 모든 row를 조인에 의해 액세스

튜닝포인트

  • 테이블간 조인횟수를 최소화 할 수 있도록 드라이빙 테이블 잘 선택해야 한다 (드라이빙 테이블의 각 row만큼 액세스하므로)
  • 드라이븐 테이블의 연결고리 칼럼에 대한 인덱스 구성 (거의 반드시)

ORDERED / LEADING은 테이블간의 접근순서를 의미하고( A를 먼저 갈까 B를 먼저갈까)

USE_NL / USE_HASH는 테이블의 접근방식을 의미하는거임( nest loop →→ / hash loop → ← 중간에서 만나냐)

// a b 순차접근으로 nest loop인데 a가 드라이빙 테이블
select /*+ ORDERED USE_NL(a b)*/ * 
from student a, teacher b
where a.id = b.id
and a.name = "abc"
and b.phone like = '%010%'

NESTED LOOPS JOIN의 장단점

  • 인덱스에 의한 랜덤 액세스에 기반하고 있기때문에 대량의 데이터 처리시 적합하지않음
  • 드라이빙 테이블로는 테이블의 데이터가 적은 마스터 테이블이거나, WHERE절 조건으로 적절하게 ROW를 제어할 수 있는 것이여야함
  • 드라이븐 테이블에는 연결컬럼을 위한 인덱스가 생성되어야함

조인 순서 제어방법

  1. 힌트사용
  • /+ ORDERED/ : FROM절에 기술한 테이블 순서대로 제어
  • /*+ LEADING(테이블명) */ : 힌트내에 제시한 테이블이 드라이빙으로 채택됨
  • 둘이 같이쓰게될때 LEADING힌트는 무시됨 (그니까 같이쓰지말라..)
  1. 뷰활용
  2. 뷰를 통해 데이터를 읽고 그데이터로 조인을 할수있음. 굳이 힌트를 안써도 뷰를 잘 활용하면 되긴함
  3. suppressing (변형)활용
  4. 변형을 가하면 인덱스를 활용할수없기때문에
  5. FROM절의 테이블 순서변경
  6. 근데 CBO는 의미없음
  • 양쪽 테이블 모두 인덱스가 있는경우
  • 두테이블중 조회되는 결과가 적은 테이블을 선택하여 드라이빙 테이블로 선택
  • 한쪽 테이블만 인덱스가 있는경우
  • 인덱스가 없는 쪽 테이블을 드라이빙 테이블로 사용함
  • 양쪽 모두 인덱스가 없는경우
  • nested loop 조인방식으로 조인이 이뤄지지않음

  • 양쪽 모두 연결고리에 대한 인덱스가 없는 경우



SORT / MERGE JOIN 사용

  • 연결고리에 인덱스가 전혀 없는 경우 (nested loop가 안되니까)
  • 대용량의 자료를 조인해야 함으로써 인덱스 사용에 따른 랜덤 액세스의 오버헤드가 많은경우
  • 드라이븐테이블이 없음 / 각 테이블에 대해 동시에 독립적으로 데이터를 각각 읽어들임
  • 읽혀진 각 테이블의 데이터를 조인을 위한 연결 고리에 대하여 정렬을 수행함
  • 정렬이 모두 끝난 후에야 조인 작업이 수행됨

튜닝포인트

  • 각 테이블로부터 데이터를 빨리 읽어 들어도록 해야함
  • 메모리(OSRT_AREA_SIZE)를 최적화해야함 ( 소팅이 둘다 끝나야 조인이 되므로 소팅이 빨리끝나야함)
// color만 인덱스라는 가정
SELECT /*+ USE_MERGE(a b)*/
			a.color, ..., b.size
FROM table_a a, table_b b
WHERE a.joinkey_a = b.joinkey_b
AND a.color = 'RED'
AND b.size = 'MED';

머지조인이므로 table_a, table_b 각각 동시에 읽어들임. a는 color가 RED인 ROW들을 인덱스를 타서 읽고, b는 size가 MED인 ROW들을 풀스캔으로 읽음
데이터를 다 읽은 후에, a는 joinkey_a를 기준으로 소팅진행, b도 joinkey_b를 기준으로 소팅하는데 둘다 소팅이 완료되어야만 그때 = 로 조인을 맺는다.
  • SORT / MERGE JOIN이 불리한경우

 

SORT / MERGE JOIN 장단점

  • 연결고리에 인덱스가 생성되어있지 않은 경우에 빠른 조인을 위하여 사용됨
  • 조인하고자 하는 가 테이블에 대해서 독립적으로 데이터를 읽어 들일 때, 이를 얼마나 빠르게 할 것 인가가 중요함. (풀스캔할때 블락을 크게잡는다던지..)
  • 각 테이블로부터 읽혀진 데이터를 연결고리에 대해 정렬을 수행할때 이를 얼마나 빠르게 할 것인가가 중요함.


HASH JOIN

NESTED LOOP - 인덱스 사용으로 인한 랜덤엑세스의 오버해더

SORT/MERGE JOIN - 정렬 작업으로 인한 오버헤드

이럴때 HASH JOIN을 사용하면 편함.

  • SORT/MERGE JOIN 조인과 비교하여, 각 테이블에 대한 처리를 독립적으로 하는것은 같지만 HASH JOIN에서는 드라이빙 테이블이 있음.다음 테이블도 똑같이 읽은 다음 해쉬값을 만들어서 기존의 해쉬값과 비교하여 조인
  • 드라이빙을 결정하여 먼저읽고 그 데이터에 대해 조인하기위해서 별도의 해쉬값을 만들어서 메모리에 올려둠 ← 이값으로 조인을함.
  • 읽어 들인 각 테이블의 데이터를 서로 조인하기 위해 해싱(hashing)을 이용해서 해시 값을 만듦
  • 다만 해시값이 충돌날수있기때문에 2차테이블까지만듬. (?)
  • 해시조인은 하드웨어가 넉넉한경우에 좋은 성능을냄. 해시에서 사용되는 메모리는 소팅에서 사용하는 메모리의 거의 2배를 필요하기때문에 많은 자원을 요구
  • 기본적으로 hash_area_size에 지정된 크기만큼의 메모리가 할당되어 사용됨.
  • 조인을 수행하기에 메모리가 부족하면 가장 큰 순서대로 hash bucket이 임시 테이블스페이스로 내려가서 구성됨.
  • 디스크로 내려간 hash bucket에 변경이 일어날때마다 디스크 i/o가 발생하여 성능이 현저하게 떨어짐

튜닝포인트

  • 드라이빙 테이블을 잘 결정해야함(nested loop처럼 적은게 나음)
  • 각 테이블로부터 데이터를 읽어 들일때, 빨리 읽을 수 있도록 해야함
  • 메모리(HASH_AREA_SIZE)를 최적화해야함 - 100건을 해쉬테이블에 올려야하는데 50건밖에 자리가없으면 나머지 50건은 디스크에 쓰기떄문


카테시안 조인의 개념

카테시안 조인이 발생하는 경우

  • where절이 없는 조인 수행
  • 조인을 위한 조건 없이 조인 수행

데이터 복제라는 개념을 활용하기 위해 사용하지만, 잘못사용하게되면 오히려 데이터를 부풀리는 원인이므로 조심해야함.

자주사용하는 방법

  • COPY_T, DUMMY_T 같은 임시테이블을 활용함.
  • DUAL을 사용함
  • 타 SQL에서 사용하고있는 테이블 활용 및 ROWNUM을 사용함

카테시안 조인 적용예제

// 각각 emp테이블을 읽어서 where조건없이 나온값들을 합치고있다 -> 이럴때 카테시안 조인을 쓰면좋음

SELECT '직군별' AS class, job, COUNT(*) AS cnt
FROM emp
GROUP BY job
UNION ALL
SELECT '부서별' AS class, TO_CHAR(deptno), COUNT(*) 
FROM emp
GROUP BY deptno
UNION ALL
SELECT '총인원' AS class, NULL, COUNT(*)
FROM emp
// 이렇게하면 조인문을 쓰지않고 위와 똑같은 결과를 낼수있음

SELECT DECODE(rn,1,'직군별',2,'부서별','총인원') as class,
	DECODE(rn,1,job,2,deptno)
	SUM(cnt)
FROM (SELECT job, deptno, COUNT(*) AS cnt
FROM emp
GROUP BY job, deptno),
(SELECT ROWNUM AS rn 
FROM (SELECT LEVEL RFROM dual
	CONNECT BY ROWNUM **<=3)**) // 3건을 복제해야하므로
GROUP BY rn,
	DECODE(rn,1,'직군별',2,'부서별','총인원'),
	DECODE(rn,1,job,2,deptno);


서브쿼리

NESTED 서브쿼리

SELECT empno, ename
FROM emp
WHERE deptno = (SELECT dptno FROM dept WHERE dname = 'SALES');

---------------------------------------------
	id | opertaion                      | name
------------------------------------------------
	0  | SELECT STATEMENT
	1  |   TABLE ACCESS BY INDEX ROWID  | EMP
	2  |      INDEX RANGE SCAN          | EMP_IDX01 (인덱스명) 인덱스를 사용했다는 의미 deptno가 인덱스겠지..
  	3  |         TABLE ACCESS FULL      | DEPT <- 서브쿼리테이블 먼저 실행

NESTED 서브쿼리는 메인쿼리보다 먼저 실행되어야 속도를 낼 수 있다.

단, 위에서 deptno가 인덱스로 주어져있을때 먼저 동작된다. 또한, 인덱스로 주어져있어도 서브쿼리에서 읽어오는 결과와 비교할때 사용되는 연산자가 인덱스를 사용할 수 없게하는 연산자라면 서브쿼리는 먼저 실행되지않는다.

CORRELATED 서브쿼리

메인쿼리보다 먼저실행되는 서브쿼리

SELECT empno, ename
FROM emp
WHERE EXISTS (SELECT 'X' FROM dept WHERE dept.deptno = emp.deptno AND dept.name = 'SALES'); 메인에서 읽어서 emp.deptno를 알려줘야만 가능

SCALAR 서브쿼리

SELECT절에도 있고 WHERE절에도 있고 ORDER BY에도 있고.. 각종 함수의 아규먼트로 사용되는 쿼리

→ 단 하나의 데이터와 단 하나의 컬럼만을 리턴함. 찾는데이터가 없으면 반드시 NULL리턴

ROLLUP(), CUBE()

  • ROLLUP, 총계를 나타내고싶을때

  • CUBE, 총계 + 소계까지 나타내고싶을때

  • CUBE랑 ROLLUP으로 다 할수있음

'IT > DB' 카테고리의 다른 글

오라클 계정내 모든 테이블 삭제하기  (0) 2024.07.15
ORACLE 정리  (0) 2023.03.27
mysql 정리  (0) 2023.03.27
mysql 도커 실행  (0) 2022.01.24
mybatis $ # 차이  (0) 2022.01.24

댓글