08. PL/SQL 함수 호출 부하 해소 방안
(1) 페이지 처리 또는 부분범위처리 활용
Select *
From (
Select memb_nm(매수회원번호) 매도 회원명
,memb_nm(매수회원번호) 매수회원명
,code_nm('446' , 매도 투자자 구분코드) 매도투자자구분명
,code_nm('446' , 매수 투자자 구분코드) 매수투자자구분명
,code_nm('418' , 체결 유형코드) 체결 유형명
. . . . . . .
From 체결
Where 종목코드 = : 종목코드
And 체결일자 = : 체결일자
And 체결시간 between sysdate-10/21/60 and sysdate
Order by 체결시각 desc
) a
Where rownum <= 30
)
Where no between 21 and 30
)
- 전체 레코드 건수만큼 호출을 일으키고 그 결과 집합을 Sort Area 또는 Temp 테이블 스페이스 에 저장한다. 그리고 최종 결과집합 10건만 전송한다.
Select memb_nm(매수회원번호) 매도 회원명
,memb_nm(매수회원번호) 매수회원명
,code_nm('446' , 매도 투자자 구분코드) 매도투자자구분명
,code_nm('446' , 매수 투자자 구분코드) 매수투자자구분명
,code_nm('418' , 체결 유형코드) 체결 유형명
. . . . . . .
From (
Select rownum no, a.*
From 매도회원번호, 매수 회원번호
, 매도투자자구분코드, 매수 투자자구분코드
. . . . . . . . . . . . .
From 체결
Where 종목코드 = : 종목코드
And 체결일자 = : 체결일자
And 체결시간 between sysdate-10/21/60 and sysdate
Order by 체결시각 desc
) a
Where rownum <= 30
)
Where no between 21 and 30
)
- Order by 와 rownum에 의한 필터 처리 후 사용자에게 전송하는 결과 집합에 대해서만 함수 호출이 일어난다.
(2) Decode 함수 또는 Case문으로 변환
- 체결 테이블 생성
CREATE TABLE 체결(체결일자, 체결번호, 시장코드, 증권그룹코드, 체결수량, 체결금액)
NOLOGGING
AS
SELECT '20090315'
, ROWNUM
, DECODE(SIGN(ROWNUM-100000), 1, 'ST', 'KQ') -- 유가증권, 코스닥
, DECODE(MOD(ROWNUM, 8), 0, 'SS', 1, 'EF', 2, 'EW' -- 주식, ETF, ELW
, 3, 'DR', 4, 'SW', 5, 'RT' -- DR, 신주인수권, 리츠
, 6, 'BC', 7, 'MF') -- 수익증권, 투자회사
, ROUND(DBMS_RANDOM.VALUE(10, 1000), -1)
, ROUND(DBMS_RANDOM.VALUE(10000, 1000000), -2)
FROM DUAL
CONNECT BY LEVEL <= 500000
UNION ALL
SELECT '20090315'
, ROWNUM + 300000
,(CASE WHEN MOD(ROWNUM, 4) < 2 THEN 'SD' ELSE 'GD' END)
,(CASE WHEN MOD(ROWNUM, 4) IN (0, 2) THEN 'FU' ELSE 'OP' END)
, ROUND(DBMS_RANDOM.VALUE(10, 1000), -1)
, ROUND(DBMS_RANDOM.VALUE(10000, 1000000), -2)
FROM DUAL
CONNECT BY LEVEL <= 500000
;
- 업무에 따라 주식 상품을 다르게 분류하고 집계함
- 집계용 쿼리를 작성할 때마다 분류 기준을 적용하기 어려워 함수 정의
CREATE OR REPLACE FUNCTION SF_상품분류(시장코드 VARCHAR2, 증권그룹코드 VARCHAR2)
RETURN VARCHAR2
IS
L_분류 VARCHAR2(20);
BEGIN
IF 시장코드 IN ('ST', 'KQ') THEN -- 유가증권, 코스닥
IF 증권그룹코드 = 'SS' THEN
L_분류 := '주식 현물';
ELSIF 증권그룹코드 IN ('EF', 'EW') THEN -- ETF, ELW
L_분류 := '파생';
ELSE
L_분류 := '주식외 현물';
END IF;
ELSE
L_분류 := '파생';
END IF;
--SELECT 순서 || '. ' || L_분류 INTO L_분류
--FROM 분류순서
--WHERE 분류명 = L_분류;
RETURN L_분류;
END;
/
SELECT *SF_상품분류(시장코드, 증권그룹코드)* 상품분류
, COUNT(*) 체결건수
, SUM(체결수량) 체결수량
, SUM(체결금액) 체결금액
FROM 체결
WHERE 체결일자 = '20090315'
GROUP BY *SF_상품분류(시장코드, 증권그룹코드)*
ORDER BY 1 ;
*경 과: 00:00:07.31*
- CASE 문으로 변경
SELECT CASE
WHEN 시장코드 IN ('ST', 'KQ') AND 증권그룹코드 = 'SS' THEN '주식 현물'
WHEN 시장코드 IN ('ST', 'KQ') AND 증권그룹코드 NOT IN ('SS', 'EF', 'EW') THEN '주식외 현물'
WHEN 시장코드 IN ('SD', 'GD') OR 증권그룹코드 IN ('EF', 'EW') THEN '파생'
END 상품분류
, COUNT(*) 체결건수
, SUM(체결수량) 체결수량
, SUM(체결금액) 체결금액
FROM 체결
WHERE 체결일자 = '20090315'
GROUP BY
CASE
WHEN 시장코드 IN ('ST', 'KQ') AND 증권그룹코드 = 'SS' THEN '주식 현물'
WHEN 시장코드 IN ('ST', 'KQ') AND 증권그룹코드 NOT IN ('SS', 'EF', 'EW') THEN '주식외 현물'
WHEN 시장코드 IN ('SD', 'GD') OR 증권그룹코드 IN ('EF', 'EW') THEN '파생'
END
ORDER BY 1 ;
*경 과: 00:00:00.68*
- DECODE 문으로 변경
SELECT DECODE( 시장코드||증권그룹코드
, 'STSS', '주식 현물'
, 'KQSS', '주식 현물'
, 'SDFU', '파생'
, 'SDOP', '파생'
, 'GDFU', '파생'
, 'GDOP', '파생'
, 'STEF', '파생'
, 'STEW', '파생'
, 'KQEF', '파생'
, 'KQEW', '파생'
, '주식외 현물' ) 상품분류
, COUNT(*) 체결건수
, SUM(체결수량) 체결수량
, SUM(체결금액) 체결금액
FROM 체결
WHERE 체결일자 = '20090315'
GROUP BY
DECODE( 시장코드||증권그룹코드
, 'STSS', '주식 현물'
, 'KQSS', '주식 현물'
, 'SDFU', '파생'
, 'SDOP', '파생'
, 'GDFU', '파생'
, 'GDOP', '파생'
, 'STEF', '파생'
, 'STEW', '파생'
, 'KQEF', '파생'
, 'KQEW', '파생'
, '주식외 현물' )
ORDER BY 1 ;
*경 과: 00:00:00.87*
- 상품분류를 사용자가 원하는 순서대로 출력하도록 메타정보 관리를 위한 테이블 생성
CREATE TABLE 분류순서(분류명, 순서)
AS
SELECT '주식 현물', 1 FROM DUAL
UNION ALL
SELECT '주식외 현물', 2 FROM DUAL
UNION ALL
SELECT '파생', 3 FROM DUAL ;
- sf_상품분류 함수에 위 테이블을 조회해 출력순서를 위한 숫자값을 앞에 붙이도록 추가
CREATE OR REPLACE FUNCTION SF_상품분류(시장코드 IN VARCHAR2, 증권그룹코드 IN VARCHAR2)
RETURN VARCHAR2
IS
L_분류 VARCHAR2(14);
BEGIN
IF 시장코드 IN ('ST', 'KQ') THEN
IF 증권그룹코드 = 'SS' THEN
L_분류 := '주식 현물';
ELSIF 증권그룹코드 IN ('EF', 'EW') THEN
L_분류 := '파생';
ELSE
L_분류 := '주식외 현물';
END IF;
ELSE
L_분류 := '파생';
END IF;
SELECT 순서 || '. ' || L_분류 INTO L_분류
FROM 분류순서
WHERE 분류명 = L_분류;
RETURN L_분류;
END;
/
-- 7 초 정도 걸리는 쿼리를 수행
SELECT SF_상품분류(시장코드, 증권그룹코드) 상품분류
, COUNT(*) 체결건수
, SUM(체결수량) 체결수량
, SUM(체결금액) 체결금액
FROM 체결
WHERE 체결일자 = '20090315'
GROUP BY SF_상품분류(시장코드, 증권그룹코드)
ORDER BY 1 ;
*경 과: 00:00:45.42*
- 45초 이상 소요
- 분류순서 테이블을 쿼리하는 Recursive Call이 100만번 수행.
CREATE TABLE 상품분류(시장코드, 증권그룹코드, 분류명)
AS
SELECT 'ST', 'SS', '주식 현물' FROM DUAL UNION ALL
SELECT 'ST', 'EF', '파생' FROM DUAL UNION ALL
SELECT 'ST', 'EW', '파생' FROM DUAL UNION ALL
SELECT 'ST', 'DR', '주식외 현물' FROM DUAL UNION ALL
SELECT 'ST', 'SW', '주식외 현물' FROM DUAL UNION ALL
SELECT 'ST', 'RT', '주식외 현물' FROM DUAL UNION ALL
SELECT 'ST', 'BC', '주식외 현물' FROM DUAL UNION ALL
SELECT 'ST', 'MF', '주식외 현물' FROM DUAL UNION ALL
SELECT 'KQ', 'SS', '주식 현물' FROM DUAL UNION ALL
SELECT 'KQ', 'EF', '파생' FROM DUAL UNION ALL
SELECT 'KQ', 'EW', '파생' FROM DUAL UNION ALL
SELECT 'KQ', 'DR', '주식외 현물' FROM DUAL UNION ALL
SELECT 'KQ', 'SW', '주식외 현물' FROM DUAL UNION ALL
SELECT 'KQ', 'RT', '주식외 현물' FROM DUAL UNION ALL
SELECT 'KQ', 'BC', '주식외 현물' FROM DUAL UNION ALL
SELECT 'KQ', 'MF', '주식외 현물' FROM DUAL UNION ALL
SELECT 'SD', 'FU', '파생' FROM DUAL UNION ALL
SELECT 'SD', 'OP', '파생' FROM DUAL UNION ALL
SELECT 'GD', 'FU', '파생' FROM DUAL UNION ALL
SELECT 'GD', 'OP', '파생' FROM DUAL ;
- 상품분류 pk 생성
ALTER TABLE 상품분류 ADD
CONSTRAINT 상품분류_PK PRIMARY KEY(시장코드, 증권그룹코드);
- 상품 분류 코드 테이블 활용
SELECT C.순서 || '. ' || B.분류명 상품분류
, SUM(체결건수) 체결건수
, SUM(체결수량) 체결수량
, SUM(체결금액) 체결금액
FROM (SELECT 시장코드, 증권그룹코드
, COUNT(*) 체결건수
, SUM(체결수량) 체결수량
, SUM(체결금액) 체결금액
FROM 체결
WHERE 체결일자 = '20090315'
GROUP BY 시장코드, 증권그룹코드) A, 상품분류 B, 분류순서 C
WHERE A.시장코드 = B.시장코드
AND A.증권그룹코드 = B.증권그룹코드
AND C.분류명 = B.분류명
GROUP BY C.순서 || '. ' || B.분류명
ORDER BY 1 ;
*경 과: 00:00:00.68*
(3) 뷰 머지(View Merge)방지를 통한 함수 호출 최소화
SELECT SUM(DECODE(SF_상품분류(시장코드, 증권그룹코드), '1. 주식 현물', 체결수량)) "주식현물_체결수량"
, SUM(DECODE(SF_상품분류(시장코드, 증권그룹코드), '2. 주식외 현물', 체결수량)) "주식외현물_체결수량"
, SUM(DECODE(SF_상품분류(시장코드, 증권그룹코드), '3. 파생', 체결수량)) "파생_체결수량"
FROM 체결
WHERE 체결일자 = '20090315' ;
*경 과: 00:02:13.51*
SELECT SUM(DECODE(상품분류, '1. 주식 현물' , 체결수량)) "주식현물_체결수량"
, SUM(DECODE(상품분류, '2. 주식외 현물', 체결수량)) "주식외현물_체결수량"
, SUM(DECODE(상품분류, '3. 파생' , 체결수량)) "파생_체결수량"
FROM (
SELECT SF_상품분류(시장코드, 증권그룹코드) 상품분류
, 체결수량
FROM 체결
WHERE 체결일자 = '20090315'
) ;
*경 과: 00:02:13.64*
SELECT SUM(DECODE(상품분류, '1. 주식 현물' , 체결수량)) "주식현물_체결수량"
, SUM(DECODE(상품분류, '2. 주식외 현물', 체결수량)) "주식외현물_체결수량"
, SUM(DECODE(상품분류, '3. 파생' , 체결수량)) "파생_체결수량"
FROM (
SELECT /*+ NO_MERGE */ SF_상품분류(시장코드, 증권그룹코드) 상품분류
, 체결수량
FROM 체결
WHERE 체결일자 = '20090315'
) ;
*경 과: 00:00:45.34*
SELECT SUM(DECODE(상품분류, '1. 주식 현물' , 체결수량)) "주식현물_체결수량"
, SUM(DECODE(상품분류, '2. 주식외 현물', 체결수량)) "주식외현물_체결수량"
, SUM(DECODE(상품분류, '3. 파생' , 체결수량)) "파생_체결수량"
FROM (
SELECT ROWNUM, SF_상품분류(시장코드, 증권그룹코드) 상품분류
, 체결수량
FROM 체결
WHERE 체결일자 = '20090315'
) ;
*경 과: 00:00:45.29*
SELECT SUM(DECODE(상품분류, '1. 주식 현물' , 체결수량)) "주식현물_체결수량"
, SUM(DECODE(상품분류, '2. 주식외 현물', 체결수량)) "주식외현물_체결수량"
, SUM(DECODE(상품분류, '3. 파생' , 체결수량)) "파생_체결수량"
FROM (
SELECT SF_상품분류(시장코드, 증권그룹코드) 상품분류
, 체결수량
FROM 체결
WHERE 체결일자 = '20090315'
AND ROWNUM > 0
) ;
*경 과: 00:00:46.50*
(4) 스칼라 서브쿼리의 캐싱효과를 이용한 함수 호출 최소화
SELECT SUM(DECODE(상품분류, '1. 주식 현물' , 체결수량)) "주식현물_체결수량"
, SUM(DECODE(상품분류, '2. 주식외 현물', 체결수량)) "주식외현물_체결수량"
, SUM(DECODE(상품분류, '3. 파생' , 체결수량)) "파생_체결수량"
FROM (
SELECT /*+ NO_MERGE */
(SELECT SF_상품분류(시장코드, 증권그룹코드) FROM DUAL) 상품분류
, 체결수량
FROM 체결
WHERE 체결일자 = '20090315'
) ;
SELECT 순서||'.'||:B1
FROM
분류순서 WHERE 분류명 = :B1
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.00 0 0 0 0
Execute 725010 11.37 10.39 0 0 0 0
Fetch 725010 17.57 17.63 0 2175030 0 725010
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 1450021 28.95 28.03 0 2175030 0 725010
*ALTER SESSION SET "_query_execution_cache_max_size" = 2097152;*
SELECT SUM(DECODE(상품분류, '1. 주식 현물' , 체결수량)) "주식현물_체결수량"
, SUM(DECODE(상품분류, '2. 주식외 현물', 체결수량)) "주식외현물_체결수량"
, SUM(DECODE(상품분류, '3. 파생' , 체결수량)) "파생_체결수량"
FROM (
SELECT /*+ NO_MERGE */
(SELECT SF_상품분류(시장코드, 증권그룹코드) FROM DUAL) 상품분류
, 체결수량
FROM 체결
WHERE 체결일자 = '20090315'
) ;
*경 과: 00:00:00.79*
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.00 0 0 0 0
Execute 20 0.00 0.00 0 0 0 0
Fetch 20 0.00 0.00 6 60 0 20
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 41 0.00 0.00 6 60 0 20
(5) Deterministic 함수의 캐싱 효과 활용
create or replace function ACCUM (p_input number) return number
*DETERMINISTIC*
as
rValue number := 0 ;
call_cnt number := 0;
begin
dbms_application_info.read_client_info(call_cnt);
if call_cnt is null then
call_cnt := 0;
end if;
*dbms_application_info.set_client_info(call_cnt + 1);*
for i in 1..p_input loop
rValue := rValue + i ;
end loop;
return rValue ;
end;
/
select sum(accum_num)
from (
select accum(mod(rownum, 50)) accum_num
from dual
connect by level <= 1000000
) ;
*경 과: 00:00:01.54*
select sys_context('userenv', 'client_info') from dual;
SYS_CONTEXT('USERENV','CLIENT_INFO')
-----------------------------------------------------------------
50
exec dbms_application_info.set_client_info( NULL );
select sum(accum_num)
from (
select accum(mod(rownum, 50)) accum_num
from dual
connect by level <= 1000000
) ;
*경 과: 00:00:13.56*
select client_info
from v$session
where sid = sys_context('userenv', 'sid');
CLIENT_INFO
-------------------------------------------------------
1000000
(6) 복잡한 함수 로직을 풀어 SQL로 구현