Skip to content
튜닝 연습PL/SQL과 사용자 정의 함수
PLSQL-015

판매가 점검 리포트만 6초, 같은 가격 함수를 쓰는 다른 두 화면은 멀쩡하다

실전SELECT유통665 / 700

상황 ​

본사 유통지원팀의 현재 판매가 함수 FN_CUR_UPRC는 세 SQL이 쓴다. POS 「상품 상세」(하루 3,000번), 매장 「가격표 출력」(하루 10번), 회계팀의 「연간 판매가 점검」(한 달에 몇 번)이다. 앞의 둘은 빠른데 점검 리포트만 6초가 걸린다. 개발 담당자는 함수에 DETERMINISTIC을 붙여 세 SQL을 한꺼번에 빠르게 하자고 제안했다. 유통지원팀장은 세 SQL을 하나씩 보고 고칠 것과 그대로 둘 것을 정해 달라고 했다.

테이블과 인덱스 ​

  • CF10 판매: 500,000행 · 4,583블록 · SLE_DT VARCHAR2(8) · 2023년 249,971건 · CF10_PK (SLE_NO)
  • CF11 판매상품: 1,096,708행 · 8,204블록 · CF11_PK (SLE_NO, ITM_NO) · 2023년 판매분 548,401행 · 그해 팔린 상품 88종
  • CF05 상품가격: 157행 · 5블록 · PK (ITM_NO, UPRC_KN_CD, APL_DT) · 판매가는 UPRC_KN_CD = '10' · 가격 개편 때 새 적용일자 행이 들어간다
  • CF01 상품: 109행 · 5블록 · 판매 중(SLE_YN = 'Y') 88종 · CF07 매장: 6행 · 4블록(매장 5곳과 본사)
  • 판매된 상품은 모두 판매가가 있다 · 통계 최신

원본 SQL ​

sql
CREATE OR REPLACE FUNCTION FN_CUR_UPRC(p_itm_no NUMBER) RETURN NUMBER IS
  v_uprc CF05.ITM_UPRC%TYPE;
BEGIN
  SELECT ITM_UPRC INTO v_uprc
    FROM CF05
   WHERE ITM_NO = p_itm_no
     AND UPRC_KN_CD = '10'
     AND APL_DT = (SELECT MAX(APL_DT) FROM CF05
                    WHERE ITM_NO = p_itm_no AND UPRC_KN_CD = '10');
  RETURN v_uprc;
END;
/

-- [Q1] POS 상품 상세 (하루 3,000번 · 한 번에 1행)
SELECT i.ITM_NO, i.ITM_NM, FN_CUR_UPRC(i.ITM_NO) AS cur_uprc
  FROM CF01 i
 WHERE i.ITM_NO = :itm_no;

-- [Q2] 매장 가격표 출력 (하루 10번 · 매장 5곳 × 판매 상품 88종 = 440행)
SELECT s.SHP_NM, i.ITM_NO, i.ITM_NM, FN_CUR_UPRC(i.ITM_NO) AS cur_uprc
  FROM CF07 s, CF01 i
 WHERE s.SHP_NO BETWEEN 1001 AND 1005
   AND i.SLE_YN = 'Y'
 ORDER BY s.SHP_NO, i.ITM_NO;

-- [Q3] 연간 판매가 점검 (상품마다 실제 판매액과 현재가로 친 금액)
SELECT d.ITM_NO,
       SUM(d.SLE_QTY)                          AS qty,
       SUM(d.SLE_QTY * d.SLE_UPRC)             AS sle_amt,
       SUM(d.SLE_QTY * FN_CUR_UPRC(d.ITM_NO))  AS cur_amt
  FROM CF10 h, CF11 d
 WHERE h.SLE_DT BETWEEN '20230101' AND '20231231'
   AND d.SLE_NO = h.SLE_NO
 GROUP BY d.ITM_NO
 ORDER BY d.ITM_NO;

실행계획 학습용 예시 ​

xplan
[Q3: DISPLAY_CURSOR (ALLSTATS LAST)]
---------------------------------------------------------------------------------------------------
| Id  | Operation           | Name | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Used-Mem |
---------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT    |      |      1 |        |     88 |00:00:06.12 |   12787 |          |
|   1 |  SORT GROUP BY      |      |      1 |     88 |     88 |00:00:06.12 |   12787 |18432  (0)|
|*  2 |   HASH JOIN         |      |      1 |    548K|    548K|00:00:00.31 |   12787 |   17M (0)|
|*  3 |    TABLE ACCESS FULL| CF10 |      1 |    250K|    249K|00:00:00.03 |    4583 |          |
|   4 |    TABLE ACCESS FULL| CF11 |      1 |   1096K|   1096K|00:00:00.07 |    8204 |          |
---------------------------------------------------------------------------------------------------
   2 - access("D"."SLE_NO"="H"."SLE_NO")
   3 - filter(("H"."SLE_DT">='20230101' AND "H"."SLE_DT"<='20231231'))

[함수 안 문장 (dep=1) · 세 SQL을 한 번씩]
          함수 호출(= 함수 안 SQL 실행)   함수 안 SQL 블록   SQL 전체 경과
  [Q1]                              1                  3          1 ms
  [Q2]                            440              1,320         12 ms
  [Q3]                        548,401          1,645,203        6.12 초

문제 ​

  1. 세 SQL에서 함수가 각각 몇 번 호출되는지 쓰고, 점검 리포트의 6초가 어디서 나오는지 계획과 함께 설명하시오.
  2. SQL마다 고칠지 그대로 둘지 정하시오. 고칠 SQL은 결과가 바뀌지 않게 다시 작성하시오.
  3. 함수에 DETERMINISTIC을 붙여 세 SQL을 한꺼번에 해결하자는 제안을 평가하시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자