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

분기 점검 스크립트가 지워도 된다고 올린 인덱스 넷을 이번 주 토요일에 그대로 지우겠다고 한다

실전DDL한빛유통68 / 700

상황 ​

한빛유통 DBA가 분기 점검에서 '접두 중복 인덱스' 찾기 스크립트를 돌렸다. 스크립트의 규칙은 이렇다. "한 인덱스의 컬럼이 다른 인덱스의 앞부분과 같으면 짧은 쪽을 지운다." 스크립트가 지울 목록 넷과 DROP 문을 만들었고, DBA는 이번 주 토요일에 그대로 돌릴 생각이다. 박팀장은 이렇게 적어 보냈다. "넷을 하나씩 따져 오고, 실제로 정리할 인덱스와 스크립트 규칙을 고쳐 와라."

테이블과 인덱스 ​

  • T_ORD 주문: 100,000행 · 896블록 · 주문 INSERT 하루 약 140건 · 결제나 취소로 ORD_ST를 고치는 UPDATE 하루 약 300건
    • PK_T_ORD (ORD_SEQ): PK 제약을 받치는 유니크 인덱스 · BLEVEL 1 · 리프 약 190블록
    • IX_T_ORD_SEQ_ST (ORD_SEQ, ORD_ST): 2019년 「주문 상태 확인」 API(SELECT ORD_ST FROM T_ORD WHERE ORD_SEQ = :o, 하루 약 5만 번)를 테이블에 가지 않게 하려고 만들었다 · 리프 약 240블록
    • IX_T_ORD_CUS (CUS_ID): 리프 약 330블록 · IX_T_ORD_CUS_DT (CUS_ID, ORD_DT): 상담 화면용 · 리프 약 375블록 · FK FK_ORD_CUS (CUS_ID → M_CUS)
  • T_ORD_DET 주문상세: 200,000행 · 896블록 · (ORD_SEQ, ORD_DET_NO) 순으로 쌓였다 · PK_T_ORD_DET (ORD_SEQ, ORD_DET_NO): BLEVEL 1 · 리프 약 500블록 · IX_T_ORD_DET_SEQ (ORD_SEQ): 리프 약 400블록 · FK FK_ORD_DET_ORD (ORD_SEQ → T_ORD)
  • T_ORD_STLM 정산: 100,000행 · 640블록 · 야간 배치가 비우고(TRUNCATE) 주문 한 건에 정산 한 건씩 다시 채운다
    • UX_STLM_ORD (ORD_SEQ): 제약 없이 CREATE UNIQUE INDEX로 만든 유니크 인덱스 · 2022년 정산 배치가 두 번 돌아 정산이 두 배로 찍힌 사고 뒤에 만들었다
    • IX_STLM_ORD_DT (ORD_SEQ, STLM_DT): 「PG 대사」 배치용(SELECT STLM_SEQ, STLM_AMT … WHERE ORD_SEQ = :o AND STLM_DT >= :d)
  • 모든 인덱스의 통계는 최신이다

원본 SQL ​

sql
-- 스크립트가 만든 DROP 문 (이번 주 토요일 실행 예정)
DROP INDEX IX_T_ORD_DET_SEQ;   -- ① (ORD_SEQ) ⊂ PK_T_ORD_DET (ORD_SEQ, ORD_DET_NO)
DROP INDEX PK_T_ORD;           -- ② (ORD_SEQ) ⊂ IX_T_ORD_SEQ_ST (ORD_SEQ, ORD_ST)
DROP INDEX IX_T_ORD_CUS;       -- ③ (CUS_ID)  ⊂ IX_T_ORD_CUS_DT (CUS_ID, ORD_DT)
DROP INDEX UX_STLM_ORD;        -- ④ (ORD_SEQ) ⊂ IX_STLM_ORD_DT (ORD_SEQ, STLM_DT)

-- 참고: 「주문 상태 확인」 API
SELECT ORD_ST FROM T_ORD WHERE ORD_SEQ = :o;

실행계획 학습용 예시 ​

xplan
[「주문 상태 확인」 API · 지금]
---------------------------------------------------------------------------------------------
| Id  | Operation        | Name            | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
---------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT |                 |      1 |        |      1 |00:00:00.01 |       2 |
|*  1 |  INDEX RANGE SCAN| IX_T_ORD_SEQ_ST |      1 |      1 |      1 |00:00:00.01 |       2 |
---------------------------------------------------------------------------------------------
   1 - access("ORD_SEQ"=:O)

[같은 API · NO_INDEX(T_ORD IX_T_ORD_SEQ_ST) 힌트로 잰 것]
----------------------------------------------------------------------------------------------
| Id  | Operation                   | Name     | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
----------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |          |      1 |        |      1 |00:00:00.01 |       3 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T_ORD    |      1 |      1 |      1 |00:00:00.01 |       3 |
|*  2 |   INDEX UNIQUE SCAN         | PK_T_ORD |      1 |      1 |      1 |00:00:00.01 |       2 |
----------------------------------------------------------------------------------------------
   2 - access("ORD_SEQ"=:O)

[주문 상세 조회 WHERE ORD_SEQ = :o]  지금 IX_T_ORD_DET_SEQ로 Buffers 3 · NO_INDEX 힌트로 PK_T_ORD_DET를 쓰게 해도 Buffers 3

문제 ​

  1. 스크립트가 올린 DROP 넷을 하나씩 판정하시오. 그대로 지워도 되는 것과 안 되는 것, 그 이유를 설명하시오.
  2. ②와 ④에서 실제로 정리할 인덱스를 정하고, 넷을 반영하는 DDL을 고쳐 쓰시오.
  3. '짧은 쪽을 지운다'는 스크립트 규칙에 더할 조건을 설명하시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자