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

영수증 목록은 4,176줄인데 합계 함수는 12,528번 돌았다

중급SELECT유통659 / 700

상황 ​

매장 관리자 화면 「월간 영수증 목록」은 영수증마다 합계, 부가세, 사은품 대상 여부를 보여 준다. 명동점 12월 목록이 0.4초쯤 걸려 다른 화면보다 눈에 띄게 늦다. SQL을 짠 개발 담당자는 "합계 함수는 인라인 뷰에서 한 번만 부르게 짰고, 바깥에서는 그 컬럼을 쓰기만 한다"라고 한다. 그런데 DBA가 본 함수 안 SQL의 실행 횟수는 화면 행 수의 정확히 세 배였다.

테이블과 인덱스 ​

  • CF10 판매(영수증): 500,000행 · 4,583블록 · SLE_DT VARCHAR2(8) · 2023년 12월 20,864건 · 그중 명동점(SHP_NO 1003) 4,176건
  • 인덱스 IX_CF10_SLE_DT (SLE_DT): 이 문제에서는 있다
  • CF11 판매상품: 1,096,708행 · 8,204블록 · CF11_PK (SLE_NO, ITM_NO) 높이 3 · 영수증 한 장에 평균 2.2행
  • 영수증에는 판매상품이 한 줄 이상 있다 · 통계 최신

원본 SQL ​

sql
CREATE OR REPLACE FUNCTION FN_SLE_AMT(p_sle_no VARCHAR2) RETURN NUMBER IS
  v_amt NUMBER;
BEGIN
  SELECT SUM(SLE_QTY * SLE_UPRC) INTO v_amt
    FROM CF11
   WHERE SLE_NO = p_sle_no;
  RETURN v_amt;
END;
/

SELECT v.SLE_NO, v.SLE_DT, v.SLE_HMS,
       v.amt,
       ROUND(v.amt / 11)                               AS vat,
       CASE WHEN v.amt >= 30000 THEN 'Y' ELSE 'N' END  AS gift_yn
  FROM (SELECT h.SLE_NO, h.SLE_DT, h.SLE_HMS,
               FN_SLE_AMT(h.SLE_NO) AS amt          -- 합계는 여기서 한 번만
          FROM CF10 h
         WHERE h.SHP_NO = :shp_no
           AND h.SLE_DT BETWEEN :fr AND :to) v
 ORDER BY v.SLE_DT, v.SLE_HMS;

실행계획 학습용 예시 ​

xplan
[DISPLAY_CURSOR (FORMAT => 'ALLSTATS LAST +PROJECTION') · :shp_no = 1003 · 2023년 12월]
---------------------------------------------------------------------------------------------------------------------
| Id  | Operation                            | Name           | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Used-Mem |
---------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                     |                |      1 |        |   4176 |00:00:00.39 |     290 |          |
|   1 |  SORT ORDER BY                       |                |      1 |   4167 |   4176 |00:00:00.39 |     290 |  407K (0)|
|*  2 |   TABLE ACCESS BY INDEX ROWID BATCHED| CF10           |      1 |   4167 |   4176 |00:00:00.01 |     290 |          |
|*  3 |    INDEX RANGE SCAN                  | IX_CF10_SLE_DT |      1 |  20833 |  20864 |00:00:00.01 |      98 |          |
---------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - filter("H"."SHP_NO"=:SHP_NO)
   3 - access("H"."SLE_DT">=:FR AND "H"."SLE_DT"<=:TO)

Column Projection Information (1번 줄만):
---------------------------------------------------
   1 - (#keys=2) "H"."SLE_DT"[VARCHAR2,8], "H"."SLE_HMS"[VARCHAR2,6], "H"."SLE_NO"[VARCHAR2,15],
       "PUP"."FN_SLE_AMT"("H"."SLE_NO")[22], ROUND("PUP"."FN_SLE_AMT"("H"."SLE_NO")/11)[22],
       CASE WHEN "PUP"."FN_SLE_AMT"("H"."SLE_NO")>=30000 THEN 'Y' ELSE 'N' END [1]

[V$SQL: 함수 안의 문장 (dep=1) · 이 실행 한 번의 증가분]
  SELECT SUM(SLE_QTY * SLE_UPRC) FROM CF11 WHERE SLE_NO = :B1    EXECUTIONS 12,528 · BUFFER_GETS 50,112 (한 번에 4블록)
[세션 통계]  recursive calls 12,537 · session logical reads 50,415

문제 ​

  1. 함수 안 SQL이 화면 행 수의 세 배로 실행된 이유를 계획에서 찾아 설명하시오.
  2. 함수가 영수증마다 한 번만 실행되도록 고치고, 더 줄일 방법이 있다면 그 SQL도 쓰시오. 각 방법의 대가도 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자