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

주문 상태 화면 넷을 인덱스 하나로 받치겠다는 설계안을 세션에서 재 보니 한 화면이 4천 블록을 넘게 읽는다

중급SELECT한빛유통72 / 700

상황 ​

조민우가 주문 상태와 관련된 화면 넷을 인덱스 하나로 받치겠다며 IX_ORD_BIG_ST_CUS_DT (ORD_ST, CUS_ID, ORD_DT)를 INVISIBLE로 만들었다. 메모는 이렇다. "선두 ORD_ST는 3종뿐이라, 상태 조건이 없는 SQL도 SKIP SCAN으로 세 번만 들어가면 된다." 세션에서 재 보니 Q1, Q2는 몇 블록이면 끝나는데 Q3은 108블록, Q4는 4,381블록이다. 박팀장은 SQL마다 이 인덱스를 어떻게 쓰는지 판정해 오고, 인덱스를 하나만 더 만들 수 있다면 무엇을 만들지 정해 오라고 했다.

테이블과 인덱스 ​

  • T_ORD_BIG: 1,000,000행 · 5,826블록 · ORD_ST COMPLETE 900,000 · PENDING 80,000 · CANCEL 20,000 · CUS_ID 2,000명(고객당 500건, 상태가 섞여 있다) · ORD_DT 1,095일(하루 913건, 전부 자정)
  • 2024-06-14: 913건 · 그중 CANCEL 18건(테이블의 서로 다른 블록에 있다)
  • 조민우 안 IX_ORD_BIG_ST_CUS_DT (ORD_ST, CUS_ID, ORD_DT): INVISIBLE · BLEVEL 2 · 리프 약 4,350블록(리프 한 블록에 약 230항목) · CANCEL 2만 항목은 리프 약 87블록 · 통계 최신
  • 이 문제에서는 다른 인덱스가 없다 · 주문은 운영에서 계속 INSERT된다(인덱스 하나에 5만 건당 redo 약 12MB, 교재 14화)

원본 SQL ​

sql
-- Q1 상담 화면 「고객 미결제 주문」 (하루 약 3천 번)
SELECT ORD_SEQ, ORD_DT, ORD_AMT
  FROM T_ORD_BIG
 WHERE ORD_ST = 'PENDING'
   AND CUS_ID = :v_cus
   AND ORD_DT >= TO_DATE(:v_from, 'YYYYMMDD');

-- Q2 상담 화면 「고객 상태별 주문 수」 (하루 약 3천 번)
SELECT ORD_ST, COUNT(*) AS ord_cnt
  FROM T_ORD_BIG
 WHERE CUS_ID = :v_cus
 GROUP BY ORD_ST;

-- Q3 정산팀 「일 취소 목록」 (하루 수십 번)
SELECT ORD_SEQ, CUS_ID, ORD_AMT
  FROM T_ORD_BIG
 WHERE ORD_ST = 'CANCEL'
   AND ORD_DT = TO_DATE(:v_dt, 'YYYYMMDD');

-- Q4 운영 상황판 「오늘 주문 건수」 (1분마다)
SELECT COUNT(*)
  FROM T_ORD_BIG
 WHERE ORD_DT = TO_DATE(:v_dt, 'YYYYMMDD');

-- 조민우 안
CREATE INDEX IX_ORD_BIG_ST_CUS_DT ON T_ORD_BIG (ORD_ST, CUS_ID, ORD_DT) INVISIBLE;

실행계획 학습용 예시 ​

xplan
[Q3 · 2024-06-14 · 세션에서 잰 것]
----------------------------------------------------------------------------------------------------------------------
| Id  | Operation                           | Name                 | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
----------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                    |                      |      1 |        |     18 |00:00:00.01 |     108 |
|   1 |  TABLE ACCESS BY INDEX ROWID BATCHED| T_ORD_BIG            |      1 |     18 |     18 |00:00:00.01 |     108 |
|*  2 |   INDEX RANGE SCAN                  | IX_ORD_BIG_ST_CUS_DT |      1 |     18 |     18 |00:00:00.01 |      90 |
----------------------------------------------------------------------------------------------------------------------
   2 - access("ORD_ST"='CANCEL' AND "ORD_DT"=TO_DATE(:V_DT,'YYYYMMDD'))
       filter("ORD_DT"=TO_DATE(:V_DT,'YYYYMMDD'))

[Q4 · 2024-06-14 · 세션에서 잰 것]
-------------------------------------------------------------------------------------------------------
| Id  | Operation             | Name                 | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
-------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |                      |      1 |        |      1 |00:00:00.08 |    4381 |
|   1 |  SORT AGGREGATE       |                      |      1 |      1 |      1 |00:00:00.08 |    4381 |
|*  2 |   INDEX FAST FULL SCAN| IX_ORD_BIG_ST_CUS_DT |      1 |    913 |    913 |00:00:00.08 |    4381 |
-------------------------------------------------------------------------------------------------------
   2 - filter("ORD_DT"=TO_DATE(:V_DT,'YYYYMMDD'))

[Q1 · CUS_000123]  INDEX RANGE SCAN · access("ORD_ST"='PENDING' AND "CUS_ID"=:V_CUS AND "ORD_DT">=…) · 테이블 3행 · Buffers 6
[Q2 · CUS_000123]  INDEX SKIP SCAN · access("CUS_ID"=:V_CUS) filter("CUS_ID"=:V_CUS) · 인덱스만 · Buffers 11

문제 ​

  1. Q1 ~ Q4가 이 인덱스를 각각 어떻게 쓰는지(access, filter, SKIP SCAN, FAST FULL SCAN) 판정하고, 그렇게 되는 이유를 설명하시오.
  2. 같은 SKIP SCAN 메모가 Q2에는 맞고 Q4에는 맞지 않는 이유를, 건너뛰어야 하는 앞 컬럼들의 조합 수로 설명하시오.
  3. 인덱스를 하나만 더 만들 수 있다면 무엇을 만들지 DDL로 작성하고, Q3과 Q4가 어떻게 바뀌는지 숫자로 설명하시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자