함수 튜닝
함수 튜닝
개발자를 위한 오라클 SQL 튜닝 내용에서 참고한 내용입니다.
Intro
분석 함수 튜닝
집계 함수의 한계
집계 함수(Aggregate Function)
SUM(), AVG(), COUNT()
SQL문에서 나온 결과 행들이 여러 개라면, 집계 함수를 사용함으로써 결과 행의 건수가 N보다 적어지는 한계가 있다.
집계 함수는 결과 행의 건수를 보장하지 않는다.
집계 내역을 뽑으면서 다른 정보도 같이 보려는 경우, 집계 함수는 적합하지 않다.
분석 함수의 유용성
분석 함수(Analytic Function)
WHERE 절을 통해 나온 행들을 대상으로 다양한 집계나 통계를 구할 때 사용하는 함수이다.
분석 함수 튜닝
분석 함수 튜닝이란 분석 함수를 이용하여 SQL문의 성능을 극대화하는 모든 활동을 의미한다.
주요 분석 함수
RANK
SELECT 문의 결과 내에서 특정 조건에 따른 순서를 구하는데, 동일한 값은 동일한 순위가 매겨진다.
SELECT RANK(/* 대상값 */) OVER (/* 순위조건 */)
FROM dual;
ROW_NUMBER
SELECT 문의 결과 내에서 특정 조건에 따른 순위를 구하는데, 동일한 값이라도 다른 순위를 매겨준다.
시퀀스를 구하는 용도로 자주 사용한다.
SELECT ROW_NUMBER(/* 대상값 */) OVER (/* 순위조건 */)
FROM DUAL;
SUM
SELECT 문 결과 내에서 특정 값의 합계를 계산하는 함수이다.
SELECT SUM(/* 대상값 */) OVER (/* 합계조건 */)
FROM DUAL;
SAL 컬럼 기준으로 정렬한 상태에서 각각의 행은 첫 행에서 현재 행까지의 합계를 산출하게 된다.
SELECT SUM() OVER (ORDER BY SAL rows BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) RANK
FROM DUAL;
MAX
SELECT 문 결과 내에서 특정 조건에 따른 최대값을 구하는 함수이다.
SELECT MAX(/* 대상값 */) OVER (/* 조건절 */)
FROM DUAL;
AVG
SELECT 문 결과 내에서 특정 조건에 따른 평균값을 구하는 함수이다.
SELECT AVG(/* 대상값 */) OVER (/* 조건절 */)
FROM DUAL;
RANK 함수를 이용하여 반복적인 테이블 스캔 제거
테이블 생성
CREATE TABLE TB_ORD
(
ORD_NO VARCHAR2(10), --주문번호
ORD_DT VARCHAR2(8), --주문일자
ORD_AMT NUMBER(15), --주문금액
PRDT_CD VARCHAR2(6), --제품코드
CUST_ID VARCHAR2(10), --고객ID
INST_ID VARCHAR2(10), --입력자
INST_DTM DATE, --입력일시
UPDT_ID VARCHAR2(10), --수정자
UPDT_DTM DATE --수정일시
);
데이터 입력
CREATE TABLE DUAL_100
(
DUMMY VARCHAR2(1)
);
INSERT INTO DUAL_100
SELECT DUMMY
FROM DUAL
CONNECT BY LEVEL <= 100;
COMMIT;
주문 테이블 NOLOGGING 모드 설정
ALTER TABLE TB_ORD
NOLOGGING;
주문 테이블에 1000만건 데이터 입력
INSERT /*+ APPEND */ INTO TB_ORD --APPEND 힌트 사용
SELECT LPAD(TO_CHAR(ROWNUM), 10, '0'),
TO_CHAR(SYSDATE - TRUNC(DBMS_RANDOM.VALUE(1, 3650)), 'YYYYMMDD'),
TRUNC(DBMS_RANDOM.VALUE(100, 1000000)),
LPAD(TO_CHAR(TRUNC(DBMS_RANDOM.VALUE(1, 999999))), 6, '0'),
LPAD(TO_CHAR(TRUNC(DBMS_RANDOM.VALUE(1, 999999))), 10, '0'),
'DBMSEXPERT',
SYSDATE,
NULL,
NULL
FROM DUAL_100,
(SELECT LEVEL LV FROM DUAL CONNECT BY LEVEL <= 100000);
COMMIT;
기본키 생성
ALTER TABLE TB_ORD
ADD CONSTRAINT TB_ORD_PK
PRIMARY KEY (ORD_NO);
통계정보 생성
ANALYZE TABLE TB_ORD COMPUTE STATISTICS
FOR TABLE FOR ALL INDEXES FOR ALL INDEXED COLUMNS SIZE 254;
분석 함수 튜닝 전 상황
SELECT Y.*
FROM (
SELECT /*+ NO_MERGE*/
ORD_DT,
MAX(ORD_AMT) AS ORD_AMT
FROM TB_ORD
WHERE ORD_DT BETWEEN TO_CHAR(SYSDATE - 30, 'YYYYMMDD') AND TO_CHAR(SYSDATE, 'YYYYMMDD')
GROUP BY ORD_DT
) X,
TB_ORD Y
WHERE Y.ORD_DT = X.ORD_DT
AND Y.ORD_AMT = X.ORD_AMT
ORDER BY Y.ORD_DT;
SQL 분석
NO_MERGE 힌트를 사용함으로써 인라인 뷰가 View Merging되어 메인쿼리인 TB_ORD Y와 같은 레벨에서 수행되는 것을 방지해준다.
즉, 인라인 뷰 X 내의 SQL은 해당 인라인 뷰 내에서 처리하게 한다.
SQL의 문제점
TB_ORD 테이블에는 별도의 인덱스가 존재하지 않지만, SQL은 TB_ORD 테이블을 2번 스캔하고 있다.
이런 SQL은 분석 함수를 이용하여 한 번만 스캔하도록 튜닝해야 한다.
실행 계획
인라인 뷰 내에 TB_ORD 테이블을 테이블 풀 스캔(TABLE ACCESS FULL)한다.
GROUP BY 연산을 수행한다.
GROUP BY 연산에 대한 인라인 뷰
TB_ORD 테이블을 테이블 풀 스캔(TABLE ACCESS FULL)한다.
GROUP BY 연산의 결과 값과 인라인 뷰 결과를 해시 조인(Hash Join)한다.
소팅 연산(Sort Order By)을 수행
SELECT 절의 연산을 수행
분석 함수 튜닝 후 상황
SELECT ORD_NO,
ORD_DT,
ORD_AMT,
PRDT_CD,
CUST_ID,
INST_ID,
INST_DTM,
UPDT_ID,
UPDT_DTM
FROM (
SELECT ORD_NO,
ORD_DT,
ORD_AMT,
PRDT_CD,
CUST_ID,
INST_ID,
INST_DTM,
UPDT_ID,
UPDT_DTM,
RANK() OVER (PARTITION BY ORD_DT ORDER BY ORD_AMT DESC) AS RN
FROM TB_ORD
WHERE ORD_DT BETWEEN TO_CHAR(SYSDATE - 30, 'YYYYMMDD') AND TO_CHAR(SYSDATE, 'YYYYMMDD'))
WHERE RN = 1
ORDER BY ORD_DT;
SQL 분석
RANK 함수를 사용하여 ORD_DT 별 ORD_AMT의 내림차순 순위를 구한다.
RANK 함수로 구한 순위 중 1위인 건만 추출한다.
실행 계획
인라인 뷰 내에 TB_ORD 테이블을 테이블 풀 스캔(TABLE ACCESS FULL) 한다.
RANK 함수 연산을 수행한다.
테이블 풀 스캔 연산의 결과에 대한 인라인 뷰이다.
SELECT 절의 연산을 수행한다.
SUM 함수를 이용하여 반복적인 테이블 스캔 제거하기
테이블 생성
CREATE TABLE TB_SALE_MONTH
(
AGENT_NO VARCHAR2(4),
YYYYMM VARCHAR2(6),
SALE_AMT NUMBER(9)
);
데이터 입력
ALTER TABLE TB_SALE_MONTH
NOLOGGING;
INSERT /*+ APPEND */ INTO TB_SALE_MONTH -- APPEND 힌트 사용
SELECT LPAD(TO_CHAR(B.LV), 4, '0'),
A.YYYYMM,
TRUNC(DBMS_RANDOM.VALUE(100000, 10000000), -3)
FROM (
SELECT TO_CHAR(ADD_MONTHS(SYSDATE, -ROWNUM + 1), 'YYYYMM') YYYYMM
FROM DUAL
CONNECT BY LEVEL <= 120
) A,
(SELECT LEVEL LV FROM DUAL CONNECT BY LEVEL <= 9999) B;
COMMIT;
기본키 생성
ALTER TABLE TB_SALE_MONTH
ADD CONSTRAINT TB_SALE_MONTH_PK
PRIMARY KEY (AGENT_NO, YYYYMM);
통계정보 생성
ANALYZE TABLE TB_SALE_MONTH COMPUTE STATISTICS
FOR TABLE FOR ALL INDEXES FOR ALL INDEXED COLUMNS SIZE 254;
튜닝 전 상황
SELECT A.AGENT_NO,
A.YYYYMM,
MIN(A.SALE_AMT),
SUM(B.SALE_AMT)
FROM TB_SALE_MONTH A,
TB_SALE_MONTH B
WHERE A.YYYYMM >= B.YYYYMM
AND A.AGENT_NO = B.AGENT_NO
GROUP BY A.AGENT_NO, A.YYYYMM
ORDER BY A.AGENT_NO, A.YYYYMM;
SQL 분석
월별 누적 합을 구하기 위해서는
B
는A
보다 작아야 한다.AGENT_NO 컬럼을 기준으로 조인 조건을 걸어준다. 이러면 AGENT_NO별 합계를 구할 수 있다.
SQL 문제점
TB_SALE_MONTH 테이블을 2번 스캔하고 있다.
SUM 분석 함수를 사용한다면 해당 테이블을 단 한번만 스캔하여 결과를 도출할 수 있다.
실행 계획
TB_SALE_MONTH 테이블을 테이블 풀 스캔(TABLE ACCESS FULL)한다.
TB_SALE_MONTH 테이블을 테이블 풀 스캔(TABLE ACCESS FULL)한다.
위의 테이블 풀 스캔한 두 결과를 해시 조인(HASH JOIN)한다.
GROUP BY 연산을 한다.
SELECT 절의 연산을 수행한다.
튜닝 후 상황
SELECT AGENT_NO,
YYYYMM,
SALE_AMT,
SUM(SALE_AMT) OVER
(PARTITION BY AGENT_NO ORDER BY AGENT_NO, YYYYMM ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
FROM TB_SALE_MONTH;
SQL 분석
분석 함수인 SUM를 이용
AGENT_NO 컬럼을 기준으로 잡는다.
AGENT_NO과 YYYYMM 컬럼을 기준으로 정렬을 수행한다.
합계 연산의 범위를 지정한다.
맨 처음부터 현재 행까지의 합계를 낸다.
실행 계획
TB_SALE_MONTH 테이블을 테이블 풀 스캔(TABLE ACCESS FULL)한다.
SUM 분석 함수를 수행한다.
SELECT 절의 연산을 수행한다.
사용자 정의 함수 튜닝
사용자 정의 함수
사용자 정의 함수(User Defined Function)는 특정 업무 프로세스를 사용자가 정의해두고 필요할 때 호출하여 사용하는 함수를 의미한다.
예시
특정 사원번호의 급여를 10% 인상한 후 인상된 급여를 리턴하는 사용자 정의 함수이다.
CREATE OR REPLACE FUNCTION FC_UPDATE_SAL(V_EMPNO IN NUMBER)
RETURN NUMBER
IS
V_SAL EMP_SAL$TYPE;
BEGIN
UPDATE EMP
SET SAL = SAL * 1.1
WHERE EMPNO = V_EMPNO;
COMMIT;
SELECT SAL
INTO V_SAL
FROM EMP
WHERE EMPNO = V_EMPNO;
RETURN V_SAL;
END;
SELECT FC_UPDATE_SAL(7369)
FROM DUAL;
사용자 정의 함수의 재귀 호출 부하
내장 함수(Built-In Function)와 사용자 정의 함수(User Defined Function)가 있다.
내장 함수는 DBMS 엔진 내에 네이티브 코드로 컴파일된 상태로 존재하므로 빠른 속도를 보장한다.
사용자 정의 함수는 PL/SQL 가상 머신 내에서 구동되어 내장 함수보다 컨텍스트 스위칭(Context-Switching) 부하가 발생한다.
이러한 부하를 재귀 호출(Recursive Call) 부하라 한다.
사용자 정의 함수 튜닝
사용자 정의 함수를 사용해야 하는 경우 성능을 고려하여 SQL 튜닝을 진행해야 한다.
사용자 정의 함수 재귀 호출 부하 최소화하기
테이블 생성
CREATE TABLE TB_EMP
(
EMP_NO VARCHAR2(10), --사원번호
EMP_NM VARCHAR2(50), --사원명
JOB_CD VARCHAR2(6), --직업코드
JOIN_DT VARCHAR2(8), --입사일자
SAL NUMBER(15), --급여
DEPT_NO VARCHAR2(6) --부서번호
);
데이터 입력
INSERT INTO TB_EMP
SELECT LPAD(TO_CHAR(ROWNUM), 10, '0'),
DBMS_RANDOM.STRING('U', 50),
LPAD(TO_CHAR(TRUNC(DBMS_RANDOM.VALUE(1, 100))), 6, '0'),
TO_CHAR(SYSDATE - TRUNC(DBMS_RANDOM.VALUE(1, 3650)), 'YYYYMMDD'),
TRUNC(DBMS_RANDOM.VALUE(1200, 12000)),
LPAD(TO_CHAR(TRUNC(DBMS_RANDOM.VALUE(1, 50))), 6, '0')
FROM DUAL
CONNECT BY LEVEL <= 100000;
COMMIT;
기본키 생성
ALTER TABLE TB_EMP
ADD CONSTRAINT TB_EMP_PK
PRIMARY KEY (EMP_NO);
인덱스 구성
CREATE INDEX TB_EMP_IDX01 ON TB_EMP (DEPT_NO);
통계 정보 생성
ANALYZE TABLE TB_EMP COMPUTE STATISTICS
FOR TABLE FOR ALL INDEXES FOR ALL INDEXED COLUMNS SIZE 254;
사용자 정의 함수 생성
CREATE OR REPLACE FUNCTION FN_GET_EMP_CNT(IN_DEPT_NO IN VARCHAR2)
RETURN NUMBER
AS
CNT NUMBER;
BEGIN
SELECT COUNT(*)
INTO CNT
FROM TB_EMP A
WHERE A.DEPT_NO = IN_DEPT_NO;
RETURN (CNT);
END;
/
사용자 정의 함수 재귀 호출 부하 최소화하기 튜닝 전
SELECT A.*,
CASE
WHEN A.EMP_CNT BETWEEN 0 AND 5
THEN '5명 이하'
WHEN A.EMP_CNT BETWEEN 6 AND 10
THEN '10명 이하'
WHEN A.EMP_CNT BETWEEN 11 AND 100
THEN '100명 이하'
WHEN A.EMP_CNT BETWEEN 101 AND 1000
THEN '1000명 이하'
ELSE '1000명 초과'
END AS 소속부서사원수
FROM (
SELECT EMP_NO,
EMP_NM,
DEPT_NO,
FN_GET_EMP_CNT(DEPT_NO) AS EMP_CNT
FROM TB_EMP
) A
ORDER BY EMP_NM;
SQL 분석
FN_GET_EMP_CNT 함수로 부서별 사원수를 구한다.
SQL 문제점
TB_EMP 테이블에는 10만건의 데이터가 존재하므로 FN_GET_EMP_CNT 함수는 총 10만번 호출된다.
이러한 경우 재귀 호출 부하가 발생하게 되어 DBMS를 장애 상황으로 몰고 갈 수 있다.
실행 계획
TB_EMP 테이블을 테이블 풀 스캔(TABLE ACCESS FULL)한다.
소팅 연산(SORT ORDER BY)을 수행한다.
SELECT 절의 연산을 수행
사용자 정의 함수 재귀 호출 부하 최소화하기 튜닝 후
SELECT A.*,
CASE
WHEN A.EMP_CNT BETWEEN 0 AND 5
THEN '5명 이하'
WHEN A.EMP_CNT BETWEEN 6 AND 10
THEN '10명 이하'
WHEN A.EMP_CNT BETWEEN 11 AND 100
THEN '100명 이하'
WHEN A.EMP_CNT BETWEEN 101 AND 1000
THEN '1000명 이하'
ELSE '1000명 초과'
END AS 소속부서사원수
FROM (
SELECT EMP_NO,
EMP_NM,
DEPT_NO,
(
SELECT FN_GET_EMP_CNT(DEPT_NO)
FROM DUAL
) AS EMP_CNT
FROM TB_EMP
) A
ORDER BY EMP_NM;
SQL 분석
사용자 정의 함수 호출 부분을 DUAL 테이블을 이용하여 스칼라 서브쿼리로 구성
스칼라 서브쿼리의 캐싱 효과로 인해 극적인 성능 향상을 이루게 된다.
스칼라 서브쿼리의 캐싱 효과 확인
서브쿼리의 캐싱 효과를 확인하기 위한 DEPT_NO의 유일 값 확인
10만 번 재귀 호출할 거를 49번으로 줄어들게 된다.
유일 값이 적은 컬럼이 사용자 정의 함수에 입력 값으로 들어가면 재귀 호출을 획기적으로 줄일 수 있다.
SELECT COUNT(DISTINCT DEPT_NO)
FROM TB_EMP;
실행 계획
TB_EMP 테이블을 테이블 풀 스캔(TABLE ACCESS FULL)한다.
소팅 연산(SORT ORDER BY)을 수행한다.
DUAL 테이블을 이용
SELECT 절의 연산을 수행한다.
Last updated
Was this helpful?