Skip to content
튜닝 연습인덱스 스캔 효율과 테이블 액세스
ACC-017

1분마다 새로 고치는 고액 주문 타일이 분기 치 인덱스를 매번 다 읽는다

실전SELECT한빛유통92 / 700

상황 ​

재무팀 대시보드의 「분기 고액 주문」 타일은 이번 분기에 49만 원 이상인 주문의 건수와 합계를 보여 준다. 49만 원은 내부통제 기준으로 정해진 값이다. 재무팀 PC 30대가 1분마다 새로 고쳐서 하루 약 18,000번 실행된다. 리스크팀의 「금액대별 분포」 화면도 같은 SQL을 쓴다. 그 화면은 기준 금액을 10만, 30만, 49만 원 가운데서 고르고 하루 수십 번 연다. 조민우의 제안은 이렇다. "금액 쪽이 더 좁은 범위니까 (ORD_AMT, ORD_DT) 인덱스를 하나 더 만들자." 박팀장은 "두 화면이 각각 어떻게 되는지, 다른 방법은 없는지 비교해 와라"라고 했다.

테이블과 인덱스 ​

  • T_ORD_BIG: 1,000,000행 · 5,826블록 · ORD_DT 2022-01-01 ~ 2024-12-30(하루 913건, 전부 자정) · 2024년 2분기 83,083건
  • ORD_AMT 1만 ~ 50만 원에 고르게 분포: 49만 원 이상 약 2.05% · 30만 원 이상 약 40.8% · 10만 원 이상 약 81.6%
  • 인덱스 IX_ORD_BIG_DT_AMT (ORD_DT, ORD_AMT): BLEVEL 2 · 리프 3,206블록(교재 04화 실측, 리프 한 블록에 약 312항목) · 통계 최신
  • 조민우 안 IX_ORD_BIG_AMT_DT (ORD_AMT, ORD_DT): 같은 크기 · INVISIBLE로 만들어 세션에서만 재 봤다
  • 운영에서는 주문이 계속 INSERT된다(교재 04화 실측: 인덱스 하나에 5만 건당 redo 약 11.8MB)

원본 SQL ​

sql
SELECT COUNT(*) AS cnt, SUM(ORD_AMT) AS amt
  FROM T_ORD_BIG
 WHERE ORD_DT  >= TO_DATE(:v_from, 'YYYYMMDD')
   AND ORD_DT  <  TO_DATE(:v_to, 'YYYYMMDD') + 1
   AND ORD_AMT >= :v_amt

실행계획 학습용 예시 · 건수는 적재 스크립트 그대로 · 금액 분포는 어림값 · 인덱스 규모는 교재 04화 실측 ​

xplan
[지금 — IX_ORD_BIG_DT_AMT · 2024년 2분기 · :v_amt = 490000]
---------------------------------------------------------------------------------------------------
| Id  | Operation          | Name              | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
---------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |                   |      1 |        |      1 |00:00:00.02 |     266 |
|   1 |  SORT AGGREGATE    |                   |      1 |      1 |      1 |00:00:00.02 |     266 |
|*  2 |   FILTER           |                   |      1 |        |   1704 |00:00:00.02 |     266 |
|*  3 |    INDEX RANGE SCAN| IX_ORD_BIG_DT_AMT |      1 |   4154 |   1704 |00:00:00.02 |     266 |
---------------------------------------------------------------------------------------------------
   2 - filter(TO_DATE(:V_TO,'YYYYMMDD')+1>TO_DATE(:V_FROM,'YYYYMMDD'))
   3 - access("ORD_DT">=TO_DATE(:V_FROM,'YYYYMMDD') AND "ORD_AMT">=:V_AMT AND "ORD_DT"<TO_DATE(:V_TO,'YYYYMMDD')+1)
       filter("ORD_AMT">=:V_AMT)

[조민우 안 — IX_ORD_BIG_AMT_DT · 세션에서만 · 같은 분기 · :v_amt = 490000]
---------------------------------------------------------------------------------------------------
| Id  | Operation          | Name              | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
---------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |                   |      1 |        |      1 |00:00:00.01 |      68 |
|   1 |  SORT AGGREGATE    |                   |      1 |      1 |      1 |00:00:00.01 |      68 |
|*  2 |   FILTER           |                   |      1 |        |   1704 |00:00:00.01 |      68 |
|*  3 |    INDEX RANGE SCAN| IX_ORD_BIG_AMT_DT |      1 |   4154 |   1704 |00:00:00.01 |      68 |
---------------------------------------------------------------------------------------------------
   3 - access("ORD_AMT">=:V_AMT AND "ORD_DT">=TO_DATE(:V_FROM,'YYYYMMDD') AND "ORD_DT"<TO_DATE(:V_TO,'YYYYMMDD')+1)
       filter(("ORD_DT">=TO_DATE(:V_FROM,'YYYYMMDD') AND "ORD_DT"<TO_DATE(:V_TO,'YYYYMMDD')+1))

[기준 금액별 Buffers — 같은 분기]
                   IX_ORD_BIG_DT_AMT (지금)    IX_ORD_BIG_AMT_DT (조민우 안)
  49만 원 이상      266                         68      (3년 치 약 20,500항목을 읽고 1,704건)
  30만 원 이상      266                         1,310   (약 408,000항목을 읽고 약 33,900건)
  10만 원 이상      267                         2,618   (약 816,000항목을 읽고 약 67,800건)

문제 ​

  1. 지금 인덱스로 1,704건을 세는 데 266블록을 읽는 이유를 3번 줄로 설명하시오. 범위 조건이 둘일 때 인덱스가 어떻게 읽히는지 정리하시오.
  2. 조민우 안을 받아들이면 두 화면이 각각 어떻게 되는지 숫자로 쓰시오. 두 화면이 같은 SQL을 쓴다는 점이 왜 문제인지도 설명하시오.
  3. 대안을 셋 이상 들어(그대로 둔다, 조민우 안, 타일 전용 SQL과 인덱스, IN 목록 등) 효과와 대가를 비교하고, 하나를 골라 SQL과 DDL을 작성하시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자