Skip to content
튜닝 연습소트 튜닝과 페이징
SORT-017

인덱스가 하나도 없는 정산 테이블에 정산 모니터 화면 SQL 다섯 개가 붙는다

실전DDL한빛유통322 / 700

상황 ​

정산 모니터 화면을 새로 만든다. 화면 SQL 다섯 개(S1 ~ S5)가 모두 T_ORD_STLM을 읽는데, 이 테이블에는 인덱스가 하나도 없다. 매일 밤 비우고 직접 경로(/*+ APPEND */)로 10만 건을 다시 싣기 때문이다(DML-001). 박팀장의 주문은 이렇다. "인덱스는 최소로. 다섯 SQL이 각각 어느 인덱스를 어떻게 쓰는지 줄 단위로 보여 주고, 밤 적재에 무엇이 늘어나는지도 적어 줘" 화면 SQL은 개발자가 쓴 그대로다. 정산 담당 20명이 낮 동안 이 화면을 연다.

테이블과 인덱스 ​

  • T_ORD_STLM 정산: 100,000행 · 640블록 · STLM_SEQ NOT NULL(정산 번호, 유일) · ORD_SEQ · CUS_ID · STLM_DT DATE NOT NULL(주문 시각을 옮긴 값, 시분초 있음, 2023-01-01 ~ 2024-12-31, 하루 약 137건) · STLM_AMT
  • 야간 배치가 TRUNCATE 뒤 T_ORD 순서대로 적재한다. 하루치 정산과 한 고객의 정산 50건은 테이블 곳곳에 흩어져 있다
  • 인덱스 없음 · 통계는 적재 직후 수집 · 화면은 낮에만 쓴다

원본 SQL ​

sql
-- S1 「최근 정산 20건」
SELECT STLM_SEQ, CUS_ID, STLM_DT, STLM_AMT
  FROM T_ORD_STLM
 ORDER BY STLM_DT DESC, STLM_SEQ DESC
 FETCH FIRST 20 ROWS ONLY;

-- S2 「고객 마지막 정산일」
SELECT MAX(STLM_DT) FROM T_ORD_STLM WHERE CUS_ID = :cus;

-- S3 「고객 최근 정산 5건」
SELECT STLM_SEQ, STLM_DT, STLM_AMT
  FROM T_ORD_STLM
 WHERE CUS_ID = :cus
 ORDER BY STLM_DT DESC, STLM_SEQ DESC
 FETCH FIRST 5 ROWS ONLY;

-- S4 「그날 고액 정산 10건」
SELECT STLM_SEQ, CUS_ID, STLM_DT, STLM_AMT
  FROM T_ORD_STLM
 WHERE STLM_DT >= :d AND STLM_DT < :d + 1
   AND ROWNUM <= 10
 ORDER BY STLM_AMT DESC;

-- S5 「정산 데이터 기간」
SELECT MIN(STLM_DT), MAX(STLM_DT) FROM T_ORD_STLM;

실행계획 학습용 예시 ​

xplan
[S1 · S2 · S3 · S5 는 TABLE ACCESS FULL 640블록 · S4 는 FULL 도중 10건에서 멈춤]
-----------------------------------------------------------------------------------------------------
| Id  | Operation                | Name       | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
-----------------------------------------------------------------------------------------------------
S1
|   0 | SELECT STATEMENT         |            |      1 |        |     20 |00:00:00.03 |     640 |
|*  1 |  VIEW                    |            |      1 |     20 |     20 |00:00:00.03 |     640 |
|*  2 |   WINDOW SORT PUSHED RANK|            |      1 |    100K|     20 |00:00:00.03 |     640 |
|   3 |    TABLE ACCESS FULL     | T_ORD_STLM |      1 |    100K|    100K|00:00:00.01 |     640 |
S2
|   0 | SELECT STATEMENT         |            |      1 |        |      1 |00:00:00.01 |     640 |
|   1 |  SORT AGGREGATE          |            |      1 |      1 |      1 |00:00:00.01 |     640 |
|*  2 |   TABLE ACCESS FULL      | T_ORD_STLM |      1 |     50 |     50 |00:00:00.01 |     640 |
S3
|   0 | SELECT STATEMENT         |            |      1 |        |      5 |00:00:00.01 |     640 |
|*  1 |  VIEW                    |            |      1 |      5 |      5 |00:00:00.01 |     640 |
|*  2 |   WINDOW SORT PUSHED RANK|            |      1 |     50 |      5 |00:00:00.01 |     640 |
|*  3 |    TABLE ACCESS FULL     | T_ORD_STLM |      1 |     50 |     50 |00:00:00.01 |     640 |
S4
|   0 | SELECT STATEMENT         |            |      1 |        |     10 |00:00:00.01 |      47 |
|   1 |  SORT ORDER BY           |            |      1 |     10 |     10 |00:00:00.01 |      47 |
|*  2 |   COUNT STOPKEY          |            |      1 |        |     10 |00:00:00.01 |      47 |
|*  3 |    FILTER                |            |      1 |        |     10 |00:00:00.01 |      47 |
|*  4 |     TABLE ACCESS FULL    | T_ORD_STLM |      1 |    137 |     10 |00:00:00.01 |      47 |
S5
|   0 | SELECT STATEMENT         |            |      1 |        |      1 |00:00:00.01 |     640 |
|   1 |  SORT AGGREGATE          |            |      1 |      1 |      1 |00:00:00.01 |     640 |
|   2 |   TABLE ACCESS FULL      | T_ORD_STLM |      1 |    100K|    100K|00:00:00.01 |     640 |
-----------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   S1 2 - filter(ROW_NUMBER() OVER ( ORDER BY INTERNAL_FUNCTION("STLM_DT") DESC ,INTERNAL_FUNCTION("STLM_SEQ") DESC )<=20)
   S2 2 - filter("CUS_ID"=:CUS)
   S3 3 - filter("CUS_ID"=:CUS)
   S4 2 - filter(ROWNUM<=10)
      3 - filter(:D+1>:D)
      4 - filter(("STLM_DT">=:D AND "STLM_DT"<:D+1))

문제 ​

  1. 다섯 SQL 가운데 결과가 틀린 SQL을 찾아 고치고, 나머지 넷이 지금 각각 무엇을 읽는지 설명하시오.
  2. 다섯 SQL을 모두 처리하는 최소 인덱스 세트를 DDL로 제안하고, SQL마다 어느 인덱스를 어떤 방식(정렬 대신, FIRST ROW, 범위를 읽은 뒤 정렬)으로 쓰는지 쓰시오. 만들지 않기로 한 인덱스와 그 이유도 쓰시오.
  3. 이 인덱스 세트가 밤 적재에 더하는 일과 그 대처를 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자