Skip to content
튜닝 연습서브쿼리와 쿼리 변환
SUBQ-027

등급 재산정 UPDATE가 C 등급 고객 1,800명을 보는 데 주문 블록 90만 개를 읽는다

중급UPDATE한빛유통262 / 700

상황 ​

매년 마지막 날 23시에 등급 재산정 배치가 돈다. 등급을 받은 날(GD_APL_DT)부터 지금까지의 주문 금액 합계가 기준(:v_amt, 4,500만 원) 이상인 C 등급 고객을 B로 올리는 UPDATE다. 올해 리허설에서 이 한 문장이 3.7초 걸렸다. 같은 시간대에 적재 배치가 돌아서 오과장은 "이 단계가 다른 배치의 버퍼를 다 밀어낸다"라고 걱정한다. SQL에는 힌트가 하나도 없는데 계획에 FILTER가 보인다.

테이블과 인덱스 ​

  • M_CUS 고객: 2,000행 · 30블록 · PK_M_CUS (CUS_ID) · C 등급 1,800명 · 이 문제에서는 등급 적용일 GD_APL_DT DATE 컬럼이 더 있다(고객마다 다르고, C 등급은 2023-07-01 ~ 2024-06-30 사이)
  • T_ORD_BIG 대용량 주문: 1,000,000행 · 5,826블록 · 고객마다 500건 · 한 고객의 주문은 테이블 전체에 흩어져 있다
  • 인덱스 IX_ORD_BIG_CUS (CUS_ID): 높이 3 · 클러스터링 팩터 약 1,000,000 · 다른 인덱스는 없다
  • C 등급 고객의 적용일 이후 주문은 고객당 평균 약 167건 · 올해 B로 올라갈 고객 212명 · 통계 최신

원본 SQL ​

sql
UPDATE M_CUS c
   SET c.CUS_GD = 'B', c.GD_APL_DT = DATE '2025-01-01'
 WHERE c.CUS_GD = 'C'
   AND (SELECT SUM(o.ORD_AMT)
          FROM T_ORD_BIG o
         WHERE o.CUS_ID  = c.CUS_ID
           AND o.ORD_DT >= c.GD_APL_DT) >= :v_amt

실행계획 학습용 예시 ​

xplan
-----------------------------------------------------------------------------------------------------------
| Id  | Operation                              | Name           | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
-----------------------------------------------------------------------------------------------------------
|   0 | UPDATE STATEMENT                       |                |      1 |        |      0 |00:00:03.71 |  906378 |
|   1 |  UPDATE                                | M_CUS          |      1 |        |      0 |00:00:03.71 |  906378 |
|*  2 |   FILTER                               |                |      1 |        |    212 |00:00:03.70 |  905430 |
|*  3 |    TABLE ACCESS FULL                   | M_CUS          |      1 |   1800 |   1800 |00:00:00.01 |      30 |
|   4 |    SORT AGGREGATE                      |                |   1800 |      1 |   1800 |00:00:03.68 |  905400 |
|*  5 |     TABLE ACCESS BY INDEX ROWID BATCHED| T_ORD_BIG      |   1800 |     25 |    300K|00:00:03.55 |  905400 |
|*  6 |      INDEX RANGE SCAN                  | IX_ORD_BIG_CUS |   1800 |    500 |    900K|00:00:00.31 |    5400 |
-----------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - filter( (SELECT SUM("O"."ORD_AMT") FROM "T_ORD_BIG" "O" WHERE "O"."CUS_ID"=:B1 AND
              "O"."ORD_DT">=:B2)>=:V_AMT)
   3 - filter("C"."CUS_GD"='C')
   5 - filter("O"."ORD_DT">=:B1)
   6 - access("O"."CUS_ID"=:B1)

문제 ​

  1. 4번에서 6번 줄의 Starts와 Buffers로 905,400블록이 어디서 나오는지 설명하시오. 힌트가 없는데도 옵티마이저가 이 서브쿼리를 펼치지 않은 이유도 설명하시오.
  2. 같은 고객 212명을 올리도록 UPDATE를 고치고, 인덱스로 푸는 방법과 비교해 어느 쪽을 택할지 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자