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

날짜 30개짜리 IN 목록으로 바꿨는데 고객 주문 이력은 여전히 100블록을 읽는다

중급SELECT한빛유통85 / 700

상황 ​

고객센터의 「고객 주문 이력」은 고객 한 명의 한 달 치 주문을 보여 준다. 상담원 서른 명이 하루 6천 번쯤 연다. 조민우는 사내 위키에서 "BETWEEN을 IN 목록으로 바꾸면 뒤 컬럼도 access가 된다"는 글을 읽고, 한 달 범위를 날짜 30개짜리 IN 목록으로 바꿨다. 실행계획에서 filter는 사라졌지만 Buffers는 128 → 104로 거의 그대로다. 아래는 고객 CUS_000123의 2024년 6월이다.

테이블과 인덱스 ​

  • T_ORD_BIG: 1,000,000행 · 5,826블록 · ORD_DT 하루 913건(전부 자정) · 2024년 6월 27,390건 · CUS_ID 2,000명(고객당 500건)
  • CUS_000123의 2024년 6월 주문: 14건(6일)
  • 인덱스 IX_ORD_BIG_DT_CUS (ORD_DT, CUS_ID): 「일 정산 대사」(ORD_DT = :d, 고객순)용 · BLEVEL 2 · 리프 약 4,100블록(리프 한 블록에 약 245항목) · CLUSTERING_FACTOR 약 1,000,000 · 통계 최신
  • 다른 인덱스 없음

원본 SQL ​

sql
-- 고치기 전
SELECT ORD_SEQ, ORD_DT, ORD_ST, ORD_AMT
  FROM T_ORD_BIG
 WHERE CUS_ID = :v_cus
   AND ORD_DT >= TO_DATE(:v_from, 'YYYYMMDD')
   AND ORD_DT <  TO_DATE(:v_to, 'YYYYMMDD') + 1;

-- 조민우가 고친 판 (화면이 그달의 날짜 수만큼 IN 목록을 만든다)
SELECT ORD_SEQ, ORD_DT, ORD_ST, ORD_AMT
  FROM T_ORD_BIG
 WHERE CUS_ID = :v_cus
   AND ORD_DT IN (TO_DATE(:d01, 'YYYYMMDD'), TO_DATE(:d02, 'YYYYMMDD'), TO_DATE(:d03, 'YYYYMMDD'),
                  /* … :d04 ~ :d29 … */
                  TO_DATE(:d30, 'YYYYMMDD'));

실행계획 학습용 예시 · 건수는 적재 스크립트 그대로 ​

xplan
[고치기 전 — 범위]
--------------------------------------------------------------------------------------------------------------------
| Id  | Operation                             | Name              | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
--------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                      |                   |      1 |        |     14 |00:00:00.01 |     128 |
|*  1 |  FILTER                               |                   |      1 |        |     14 |00:00:00.01 |     128 |
|   2 |   TABLE ACCESS BY INDEX ROWID BATCHED | T_ORD_BIG         |      1 |     14 |     14 |00:00:00.01 |     128 |
|*  3 |    INDEX RANGE SCAN                   | IX_ORD_BIG_DT_CUS |      1 |     14 |     14 |00:00:00.01 |     114 |
--------------------------------------------------------------------------------------------------------------------
   1 - filter(TO_DATE(:V_TO,'YYYYMMDD')+1>TO_DATE(:V_FROM,'YYYYMMDD'))
   3 - access("ORD_DT">=TO_DATE(:V_FROM,'YYYYMMDD') AND "CUS_ID"=:V_CUS AND "ORD_DT"<TO_DATE(:V_TO,'YYYYMMDD')+1)
       filter("CUS_ID"=:V_CUS)

[조민우 판 — IN 목록 30개]
--------------------------------------------------------------------------------------------------------------------
| Id  | Operation                             | Name              | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
--------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                      |                   |      1 |        |     14 |00:00:00.01 |     104 |
|   1 |  INLIST ITERATOR                      |                   |      1 |        |     14 |00:00:00.01 |     104 |
|   2 |   TABLE ACCESS BY INDEX ROWID BATCHED | T_ORD_BIG         |     30 |     14 |     14 |00:00:00.01 |     104 |
|*  3 |    INDEX RANGE SCAN                   | IX_ORD_BIG_DT_CUS |     30 |     14 |     14 |00:00:00.01 |      90 |
--------------------------------------------------------------------------------------------------------------------
   3 - access((("ORD_DT"=TO_DATE(:D01,'YYYYMMDD') OR "ORD_DT"=TO_DATE(:D02,'YYYYMMDD') OR … OR
              "ORD_DT"=TO_DATE(:D30,'YYYYMMDD'))) AND "CUS_ID"=:V_CUS)

문제 ​

  1. IN 목록으로 바꾼 뒤 filter가 사라졌는데도 Buffers가 거의 줄지 않은 이유를, 고치기 전 3번 줄이 읽은 양과 고친 뒤 3번 줄의 Starts로 설명하시오.
  2. 이 화면에 맞는 해법을 SQL과 인덱스 DDL로 작성하시오.
  3. BETWEEN을 IN 목록으로 바꾸는 방법이 효과를 보는 조건을 정리하고, 날짜 수만큼 IN 목록을 만드는 방식의 부작용도 설명하시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자