Skip to content
튜닝 연습조인 튜닝
JOIN-084

인수받은 판매 마감 패키지의 SQL 넷을 점검하는데 Hint Report가 넷 다 다르게 찍힌다

실전INSERT유통234 / 700

상황 ​

본사 레거시 유통의 판매 마감 패키지 PKG_SLE_DAY를 이번 달부터 튜닝팀이 맡았다. 박팀장은 인수 첫 주에 패키지 안의 힌트를 모두 점검하라고 했다. 힌트가 있는 SQL은 넷이고, 오과장이 운영에서 SQL마다 실행계획과 Hint Report를 떠 왔다. 박팀장의 지시는 이렇다. "SQL마다 힌트가 먹었는지, 먹었다면 맞는지 판정하고, 고칠 것은 고쳐 오세요. 같은 일이 안 생기게 팀 규칙도 같이."

테이블과 인덱스 ​

  • CF10 판매: 500,000행 · 4,583블록 · CF10_PK (SLE_NO) · IX_CF10_SLE_DT (SLE_DT) · 날짜순 적재 · 하루 약 685건 · 매장 6곳
  • CF11 판매상품: 1,096,708행 · 8,204블록 · CF11_PK (SLE_NO, ITM_NO) · 영수증당 평균 2.19줄
  • SLE_DAY_ITM 일 상품 마감: S1의 적재 대상 · 매일 밤 한 번 적재하고 낮에는 읽기만 한다
  • 패키지는 PUP 계정 소유다. S3만 다른 계정의 프로그램에서 옮겨 와서 테이블 앞에 스키마 이름을 붙여 썼다
  • 통계 최신 · SLE_DT가 VARCHAR2(8)이라 범위 조건은 실제의 4분의 1 안팎으로 추정된다(교재 03화)

원본 SQL ​

sql
-- [S1] 일 상품 마감 적재 (매일 밤 한 번, 전날 하루)
INSERT /*+ APPEND LEADING(a b) USE_HASH(b) */ INTO SLE_DAY_ITM (SLE_DT, SHP_NO, ITM_NO, SLE_QTY, SLE_AMT)
SELECT a.SLE_DT, a.SHP_NO, b.ITM_NO, SUM(b.SLE_QTY), SUM(b.SLE_QTY * b.SLE_UPRC)
  FROM CF10 a, CF11 b
 WHERE a.SLE_DT = :d
   AND b.SLE_NO = a.SLE_NO
 GROUP BY a.SLE_DT, a.SHP_NO, b.ITM_NO;

-- [S2] 매장 일 영수증 상세 (점장 화면, 하루 2천 번)
SELECT /*+ ORDERED USE_HASH(a) LEADING(a b) USE_NL(b) */
       a.SLE_NO, a.SLE_HMS, b.ITM_NO, b.SLE_QTY, b.SLE_UPRC
  FROM CF11 b, CF10 a
 WHERE a.SLE_DT = :d
   AND a.SHP_NO = :shp
   AND b.SLE_NO = a.SLE_NO
 ORDER BY a.SLE_NO, b.ITM_NO;

-- [S3] 주간 상품 판매 (영업팀, 하루 수십 번)
SELECT /*+ LEADING(PUP.CF10 PUP.CF11) USE_HASH(PUP.CF11) FULL(PUP.CF11) */
       PUP.CF11.ITM_NO, SUM(PUP.CF11.SLE_QTY) AS qty
  FROM PUP.CF10, PUP.CF11
 WHERE PUP.CF10.SLE_DT BETWEEN :d1 AND :d2
   AND PUP.CF11.SLE_NO = PUP.CF10.SLE_NO
 GROUP BY PUP.CF11.ITM_NO;

-- [S4] 영수증 한 장 상세 (고객센터, 하루 3천 번)
SELECT /*+ LEADING(a b) USE_NL(b) */
       a.SLE_NO, a.SLE_DT, a.SLE_HMS, b.ITM_NO, b.SLE_QTY, b.SLE_UPRC
  FROM CF10 a, CF11 b
 WHERE a.SLE_NO = :sle_no
   AND b.SLE_NO = a.SLE_NO;

실행계획 학습용 예시 ​

xplan
[요약 · 운영 실측]
 SQL  Hint Report                                         계획                                        Buffers    시간
 S1   APPEND 쓰임 · LEADING(a b), USE_HASH(b)는 N         LOAD AS SELECT ← NL (CF10 하루 → CF11_PK)     3,573   0.02초
 S2   ORDERED, USE_HASH(a) 쓰임 · LEADING(a b), USE_NL(b)는 U   HASH JOIN (CF11 FULL이 build)          8,222   0.46초
 S3   LEADING, USE_HASH, FULL 셋 다 N                     NL (CF10 일주일 → CF11_PK)                  24,997   0.19초
 S4   LEADING(a b), USE_NL(b) 쓰임                        NL · 힌트를 뗀 판도 같은 plan hash value          9   0.00초
 (참고) S1의 SELECT만 떼어 USE_HASH(b)로 돌리면 8,222블록

[S1 · Hint Report]
Total hints for statement: 3 (N - Unresolved (2))
   1 -  INS$1
         N -  LEADING(a b)
         N -  USE_HASH(b)
   1 -  INS$1 / SLE_DAY_ITM@INS$1
           -  APPEND

[S2 · Hint Report]
Total hints for statement: 4 (U - Unused (2))
   1 -  SEL$1
         U -  LEADING(a b)
           -  ORDERED
   3 -  SEL$1 / "B"@"SEL$1"
         U -  USE_NL(b)
   4 -  SEL$1 / "A"@"SEL$1"
           -  USE_HASH(a)

[S3 · Hint Report]
Total hints for statement: 3 (N - Unresolved (3))
   1 -  SEL$1
         N -  FULL(PUP.CF11)
         N -  LEADING(PUP.CF10 PUP.CF11)
         N -  USE_HASH(PUP.CF11)

------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                              | Name           | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Used-Mem |
------------------------------------------------------------------------------------------------------------------------
[S2 · 매장 1003, 2023-12-24]
|   0 | SELECT STATEMENT                       |                |      1 |        |    251 |00:00:00.46 |    8222 |          |
|   1 |  SORT ORDER BY                         |                |      1 |    251 |    251 |00:00:00.46 |    8222 | 2048  (0)|
|*  2 |   HASH JOIN                            |                |      1 |    251 |    251 |00:00:00.46 |    8222 |   38M (0)|
|   3 |    TABLE ACCESS FULL                   | CF11           |      1 |  1096K |  1096K |00:00:00.11 |    8204 |          |
|*  4 |    TABLE ACCESS BY INDEX ROWID BATCHED | CF10           |      1 |    114 |    114 |00:00:00.01 |      18 |          |
|*  5 |     INDEX RANGE SCAN                   | IX_CF10_SLE_DT |      1 |    685 |    685 |00:00:00.01 |       4 |          |
[S3 · 2023-12-18 ~ 2023-12-24]
|   0 | SELECT STATEMENT                       |                |      1 |        |    109 |00:00:00.19 |   24997 |          |
|   1 |  HASH GROUP BY                         |                |      1 |    109 |    109 |00:00:00.19 |   24997 | 1210K (0)|
|   2 |   NESTED LOOPS                         |                |      1 |   2626 |  10501 |00:00:00.18 |   24997 |          |
|   3 |    NESTED LOOPS                        |                |      1 |   2626 |  10501 |00:00:00.09 |   14497 |          |
|   4 |     TABLE ACCESS BY INDEX ROWID BATCHED| CF10           |      1 |   1199 |   4795 |00:00:00.01 |     112 |          |
|*  5 |      INDEX RANGE SCAN                  | IX_CF10_SLE_DT |      1 |   1199 |   4795 |00:00:00.01 |      14 |          |
|*  6 |     INDEX RANGE SCAN                   | CF11_PK        |   4795 |      2 |  10501 |00:00:00.06 |   14385 |          |
|   7 |    TABLE ACCESS BY INDEX ROWID         | CF11           |  10501 |      2 |  10501 |00:00:00.08 |   10500 |          |
------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
  [S2]  2 - access("B"."SLE_NO"="A"."SLE_NO")
        4 - filter("A"."SHP_NO"=:SHP)
        5 - access("A"."SLE_DT"=:D)
  [S3]  5 - access("CF10"."SLE_DT">=:D1 AND "CF10"."SLE_DT"<=:D2)
        6 - access("CF11"."SLE_NO"="CF10"."SLE_NO")

문제 ​

  1. 네 SQL의 힌트를 Hint Report와 실행계획으로 각각 판정하시오(대상을 못 찾음, 찾았지만 못 씀, 쓰였지만 틀림, 쓰였고 맞음). 판정마다 그 원인을 쓰시오.
  2. 고칠 SQL은 고쳐 쓰고, 그대로 두거나 지울 힌트는 그 근거를 숫자로 쓰시오.
  3. 같은 일이 되풀이되지 않도록 팀의 힌트 작성 규칙을 제안하시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자