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

결합 인덱스를 새로 만들었는데 2018년 패키지의 두 SQL만 계획이 그대로다

실전SELECT한빛유통20 / 700

상황 ​

한빛유통 주문 모니터링 패키지 PKG_ORD_MON은 2018년에 강도현이 짰다. 작년에 고객센터용 (CUS_ID, ORD_DT) 인덱스와 미결제 대시보드용 (ORD_ST, ORD_DT) 인덱스가 생겼는데, 이 패키지의 두 SQL은 계획이 그대로다. 소스에는 -- 인덱스 타지 말 것 (2018.05 강도현)이라는 주석과 함께 컬럼에 || ''와 + 0이 붙어 있다. 그해 회의록에는 이렇게 적혀 있다. "고객 인덱스를 타면 B2B 대량 고객에서 느림 → 주문일 인덱스로 유도. 주간 범위를 주문일 인덱스로 읽으면 느림 → 상태 인덱스로 유도." 그때 인덱스는 단일 컬럼 (CUS_ID), (ORD_ST), (ORD_DT) 셋이었다.

테이블과 인덱스 ​

  • T_ORD_BIG: 1,000,000행 · 5,826블록 · 하루 913건이 서로 다른 블록에 흩어져 있다
  • CUS_ID 2,000명 · 고객마다 주문 500건(2019년에 B2B 사업을 접어 대량 고객이 없다) · 고객 CUS_000123의 2024년 4 ~ 6월 주문 41건 · 같은 석 달의 전체 주문 83,083건
  • ORD_ST COMPLETE 90% · PENDING 8%(80,000건) · CANCEL 2% · 2024-06-03 ~ 06-09 주간 6,391건 중 PENDING 731건
  • 지금 인덱스: IX_ORD_BIG_CUS_DT (CUS_ID, ORD_DT) · IX_ORD_BIG_ST_DT (ORD_ST, ORD_DT) · IX_ORD_BIG_DT_AMT (ORD_DT, ORD_AMT): 모두 BLEVEL 2 · ORD_ST에 히스토그램 · 통계 최신(2018년의 단일 컬럼 인덱스는 지금 없다)

원본 SQL ​

sql
-- A. 고객 최근 주문 (고객센터 화면 · 하루 약 2만 번)
SELECT ORD_SEQ, ORD_DT, ORD_ST, ORD_AMT
  FROM T_ORD_BIG
 WHERE CUS_ID || '' = :v_cus                         -- 인덱스 타지 말 것 (2018.05 강도현)
   AND ORD_DT >= TO_DATE(:v_from, 'YYYYMMDD')
   AND ORD_DT <  TO_DATE(:v_to, 'YYYYMMDD') + 1
 ORDER BY ORD_DT DESC;

-- B. 미결제 주간 점검 (매시 정각 배치)
SELECT ORD_SEQ, CUS_ID, ORD_DT, ORD_AMT
  FROM T_ORD_BIG
 WHERE ORD_ST = 'PENDING'
   AND ORD_DT + 0 >= TO_DATE(:v_from, 'YYYYMMDD')    -- 인덱스 타지 말 것 (2018.05 강도현)
   AND ORD_DT + 0 <  TO_DATE(:v_to, 'YYYYMMDD') + 1;

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

xplan
[A — CUS_000123 · 2024-04-01 ~ 2024-06-30]
----------------------------------------------------------------------------------------
| Id  | Operation           | Name      | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
----------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT    |           |      1 |        |     41 |00:00:00.09 |    5826 |
|   1 |  SORT ORDER BY      |           |      1 |    831 |     41 |00:00:00.09 |    5826 |
|*  2 |   FILTER            |           |      1 |        |     41 |00:00:00.09 |    5826 |
|*  3 |    TABLE ACCESS FULL| T_ORD_BIG |      1 |    831 |     41 |00:00:00.09 |    5826 |
----------------------------------------------------------------------------------------
   2 - filter(TO_DATE(:V_TO,'YYYYMMDD')+1>TO_DATE(:V_FROM,'YYYYMMDD'))
   3 - filter(("CUS_ID"||''=:V_CUS AND "ORD_DT">=TO_DATE(:V_FROM,'YYYYMMDD') AND
              "ORD_DT"<TO_DATE(:V_TO,'YYYYMMDD')+1))

[B — 2024-06-03 ~ 2024-06-09]
-----------------------------------------------------------------------------------------------------------------
| Id  | Operation                            | Name             | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
-----------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                     |                  |      1 |        |    731 |00:00:00.02 |    1046 |
|   1 |  TABLE ACCESS BY INDEX ROWID BATCHED | T_ORD_BIG        |      1 |    400 |    731 |00:00:00.02 |    1046 |
|*  2 |   INDEX RANGE SCAN                   | IX_ORD_BIG_ST_DT |      1 |    400 |    731 |00:00:00.01 |     315 |
-----------------------------------------------------------------------------------------------------------------
   2 - access("ORD_ST"='PENDING')
       filter(("ORD_DT"+0>=TO_DATE(:V_FROM,'YYYYMMDD') AND "ORD_DT"+0<TO_DATE(:V_TO,'YYYYMMDD')+1))

문제 ​

  1. A와 B에 붙은 || ''와 + 0이 지금 인덱스에서 각각 무엇을 막고 있는지 실행계획의 access와 filter로 설명하시오. 같은 종류의 가공인데 손해의 크기가 크게 다른 이유도 설명하시오.
  2. 2018년에 이 가공을 넣은 이유가 지금도 유효한지 따지고, 두 SQL을 고치시오.
  3. 패키지의 다른 SQL에도 같은 장치가 남아 있는지 찾는 방법과, 떼어 낸 뒤 확인할 것을 설명하시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자