Skip to content
튜닝 연습인덱스 사용 조건
IDX-036

부정형을 고치라는 코드 리뷰대로 고쳤더니 월 매출 집계가 두 배 넘게 읽는다

기초SELECT한빛유통36 / 700

상황 ​

정산팀 「월 매출(취소 제외)」 화면은 한 달 동안 취소되지 않은 주문의 건수와 금액을 낸다. 정산 담당 8명이 하루 종일 연다. 코드 리뷰에서 "부정형(<>)은 인덱스를 못 탄다"는 지적을 받은 조민우가 조건을 긍정형 IN으로 바꿨다. 그래도 계획이 FULL이라 INDEX 힌트까지 붙였더니 오히려 느려졌다. 두 판의 계획이 아래에 있다(2024년 6월).

테이블과 인덱스 ​

  • T_ORD_BIG: 1,000,000행 · 5,826블록 · ORD_DT DATE(전부 자정) · ORD_ST COMPLETE 90% · PENDING 8% · CANCEL 2% · ORD_AMT
  • 2024년 6월 주문 27,390건 · 그중 CANCEL 548건 · 취소 제외 26,842건
  • 인덱스 IX_ORD_BIG_DT (ORD_DT): 리프 2,653 · IX_ORD_BIG_ST (ORD_ST): BLEVEL 2 · 리프 2,775 · 통계 최신
  • 같은 상태값의 인덱스 항목은 ROWID 순으로 놓여 있다. 상태 하나의 범위를 따라 테이블로 가면 블록을 앞에서부터 차례로 한 번씩 거친다

원본 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_ST <> 'CANCEL';

-- [조민우의 수정안]
SELECT /*+ INDEX(T_ORD_BIG IX_ORD_BIG_ST) */ 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_ST IN ('COMPLETE', 'PENDING');

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

xplan
[처음 판 · ORD_ST <> 'CANCEL']
-------------------------------------------------------------------------------------------------------------
| Id  | Operation                            | Name          | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
-------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                     |               |      1 |        |      1 |00:00:00.05 |    5826 |
|   1 |  SORT AGGREGATE                      |               |      1 |      1 |      1 |00:00:00.05 |    5826 |
|*  2 |   TABLE ACCESS FULL                  | T_ORD_BIG     |      1 |  27769 |  26842 |00:00:00.05 |    5826 |
-------------------------------------------------------------------------------------------------------------
   2 - filter(("ORD_DT">=TO_DATE(:V_FROM,'YYYYMMDD') AND "ORD_ST"<>'CANCEL' AND
              "ORD_DT"<TO_DATE(:V_TO,'YYYYMMDD')+1))

[조민우의 수정안 · IN + INDEX 힌트]
-------------------------------------------------------------------------------------------------------------
| Id  | Operation                            | Name          | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
-------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                     |               |      1 |        |      1 |00:00:00.41 |   14382 |
|   1 |  SORT AGGREGATE                      |               |      1 |      1 |      1 |00:00:00.41 |   14382 |
|   2 |   INLIST ITERATOR                    |               |      1 |        |  26842 |00:00:00.41 |   14382 |
|*  3 |    TABLE ACCESS BY INDEX ROWID BATCHED| T_ORD_BIG    |      2 |  27769 |  26842 |00:00:00.40 |   14382 |
|*  4 |     INDEX RANGE SCAN                 | IX_ORD_BIG_ST |      2 |    980K|    980K|00:00:00.09 |    2722 |
-------------------------------------------------------------------------------------------------------------
   3 - filter(("ORD_DT">=TO_DATE(:V_FROM,'YYYYMMDD') AND "ORD_DT"<TO_DATE(:V_TO,'YYYYMMDD')+1))
   4 - access(("ORD_ST"='COMPLETE' OR "ORD_ST"='PENDING'))

문제 ​

  1. 처음 판이 IX_ORD_BIG_ST를 쓰지 않은 이유와, 수정안이 인덱스를 쓰고도 처음 판보다 많이 읽은(14,382블록) 이유를 실행계획의 숫자로 설명하시오.
  2. 결과가 바뀌지 않게 SQL과 인덱스를 고쳐 6월 범위만 읽도록 하시오. 취소 제외 조건은 어디에 두면 되는지도 설명하시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자