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

협력사 포털의 상품별 판매 조회가 상품을 하나 더 고를 때마다 0.11초씩 느려진다

중급PLSQL유통674 / 700

상황 ​

본사 유통의 협력사 포털 「상품별 월 판매」는 협력사 담당자가 자기 상품 가운데 볼 것을 체크하고 월을 고르면 상품마다 판매 수량과 금액을 보여 준다. 포털(Java)은 체크한 상품 번호를 SQL 컬렉션 타입 ITM_NO_TAB 하나로 바인드해 프로시저 PTNR_ITM_SALES를 부른다. 푸드 협력사(1005)는 상품 21종을 모두 체크하는 일이 많은데, 그러면 2.34초가 걸린다. 3종만 고르면 0.34초다. 개발자는 이렇게 말한다. "고르는 상품 수가 매번 달라서 한 문장으로는 못 짜고 상품마다 돌렸어요."

테이블과 인덱스 ​

  • CF10 판매: 500,000행 · 4,583블록 · CF10_PK (SLE_NO) · SLE_DT VARCHAR2(8) · 2023년 12월 20,864건 · SLE_DT로 시작하는 인덱스는 이 문제에서 없다
  • CF11 판매상품: 1,096,708행 · 8,204블록 · CF11_PK (SLE_NO, ITM_NO) · ITM_NO로 시작하는 인덱스는 없다 · 2023년 12월 46,322줄
  • 협력사 1005의 상품 21종(ITM_NO 1047 ~ 1067): 판매상품 105,736줄(2년 치) · 2023년 12월 4,536줄
  • ITM_NO_TAB: CREATE TYPE ITM_NO_TAB AS TABLE OF NUMBER로 만든 SQL 타입(스키마에 있다)
  • TMP_PTNR_SALES: 임시 테이블(GTT) · ITM_NO · QTY · AMT
  • 판매가 없는 상품은 수량과 금액을 0으로 보여 준다(지금 프로시저가 그렇게 한다) · 통계 최신

원본 SQL ​

sql
PROCEDURE PTNR_ITM_SALES(p_itms IN ITM_NO_TAB, p_ym IN VARCHAR2, p_cur OUT SYS_REFCURSOR) IS
  v_qty NUMBER;
  v_amt NUMBER;
BEGIN
  DELETE FROM TMP_PTNR_SALES;
  FOR i IN 1 .. p_itms.COUNT LOOP                      -- 고른 상품마다
    SELECT NVL(SUM(d.SLE_QTY), 0), NVL(SUM(d.SLE_QTY * d.SLE_UPRC), 0)
      INTO v_qty, v_amt
      FROM CF10 h, CF11 d
     WHERE h.SLE_DT BETWEEN p_ym || '01' AND p_ym || '31'
       AND d.SLE_NO = h.SLE_NO
       AND d.ITM_NO = p_itms(i);
    INSERT INTO TMP_PTNR_SALES (ITM_NO, QTY, AMT) VALUES (p_itms(i), v_qty, v_amt);
  END LOOP;
  OPEN p_cur FOR SELECT ITM_NO, QTY, AMT FROM TMP_PTNR_SALES ORDER BY ITM_NO;
END;

실행계획 학습용 예시 · 건수와 금액은 원본 데이터로 셈 ​

xplan
[포털 로그: 고른 상품 수별 경과 (2023년 12월)]   3종 0.34초 · 7종 0.78초 · 21종 2.34초

[V$SQL: 21종을 고른 호출 한 번]
  SQL_TEXT                                                                   EXECUTIONS  BUFFER_GETS  ELAPSED
  SELECT NVL(SUM(D.SLE_QTY), 0), NVL(SUM(D.SLE_QTY * D.SLE_UPRC), 0) FROM …          21      268,527   2.31 초
  INSERT INTO TMP_PTNR_SALES (ITM_NO, QTY, AMT) VALUES (:B3 , :B2 , :B1 )            21           24   0.00 초
  DELETE FROM TMP_PTNR_SALES                                                          1            3   0.00 초
  SELECT ITM_NO, QTY, AMT FROM TMP_PTNR_SALES ORDER BY ITM_NO                         1            3   0.00 초

[루프 안 SELECT 한 번: ITM_NO = 1067 (DISPLAY_CURSOR · ALLSTATS LAST)]
--------------------------------------------------------------------------------------------------
| Id  | Operation           | Name | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Used-Mem |
--------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT    |      |      1 |        |      1 |00:00:00.11 |   12787 |          |
|   1 |  SORT AGGREGATE     |      |      1 |      1 |      1 |00:00:00.11 |   12787 |          |
|*  2 |   HASH JOIN         |      |      1 |    519 |    272 |00:00:00.11 |   12787 | 1557K (0)|
|*  3 |    TABLE ACCESS FULL| CF11 |      1 |  12463 |   6247 |00:00:00.06 |    8204 |          |
|*  4 |    TABLE ACCESS FULL| CF10 |      1 |  20833 |  20864 |00:00:00.03 |    4583 |          |
--------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - access("D"."SLE_NO"="H"."SLE_NO")
   3 - filter("D"."ITM_NO"=:B1)
   4 - filter(("H"."SLE_DT">=:B3||'01' AND "H"."SLE_DT"<=:B2||'31'))

문제 ​

  1. 고르는 상품이 많을수록 느려지는 이유를 V$SQL과 실행계획의 숫자로 설명하시오.
  2. 고른 상품 수와 상관없이 판매와 판매상품을 한 번씩만 읽도록 프로시저를 고치시오. 판매가 없는 상품도 지금처럼 0으로 나오게 하시오.
  3. 고친 문장의 실행계획에서 컬렉션 줄의 E-Rows가 무엇을 뜻하는지, 어떤 문장에서 문제가 되고 그때 어떻게 다루는지 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자