Skip to content
튜닝 연습인덱스 스캔 효율과 테이블 액세스
ACC-005

개발팀이 WHERE절에 나오는 컬럼마다 인덱스를 하나씩 달라고 한다

실전DDL이커머스80 / 700

이 문제는 교재 18화 졸업 과제와 같은 이커머스 데이터베이스를 씁니다. 『튜너의 1년』을 순서대로 읽고 있다면 졸업 과제를 먼저 풀고 오세요. 아래 「테이블과 인덱스」 칸에 과제의 답 일부(제약 조건, 인덱스, 통계의 실제 상태)가 나옵니다.

상황 ​

신규 이커머스 플랫폼을 오픈하기 전에 점검하고 있다. 개발팀이 화면 SQL 셋과 함께 인덱스 요청서를 보냈는데, 내용은 이렇다. "WHERE에 나오는 컬럼마다 하나씩: (customer_id), (order_date), (status), (order_id)" 같은 주에 정합성 회복 계획도 확정됐다. 꺼져 있는 FK FK_ORDERS_CUSTOMER_ID를 다시 켜고, 매달 휴면 고객 정리 배치가 주문이 없는 고객을 customers에서 지운다. 주문 화면은 배치가 도는 시간에도 주문을 넣는다. 박팀장은 이렇게 당부했다. "최소한으로. 무엇을 만들고 무엇을 안 만드는지, 각각 왜인지."

테이블과 인덱스 ​

  • orders: 34,908행 · 193블록 · order_date DATE(시분초 있음, 2020 ~ 2024, 하루 약 20건) · status 6종(히스토그램 없음) · total_amount
  • 고객 5,230명 · 고객당 주문 평균 6.7건(고객 390은 16건) · 2024년 6월 주문 563건
  • 인덱스: PK (order_id) 하나뿐(실행계획에는 PK_ORDERS로 적는다) · FK 39개 전부 DISABLED(FK_ORDERS_CUSTOMER_ID 포함)
  • 참고 실측(교재 18화): (customer_id, order_date) 인덱스는 리프 112 · CF 34,730 · 1MB이고, 만드는 데 0.1초 안팎 걸린다. orders 복사본에 34,908행을 일반 INSERT하면 redo 1.56MB, 블록 변경 1,816이다. 여기에 그 인덱스 하나를 더하면 redo 약 12.1MB, 블록 변경 약 72,600으로 늘어난다

원본 SQL ​

sql
-- S1 「고객 주문 내역」 — 고객센터 · 하루 종일
SELECT order_id, order_date, status, total_amount
  FROM orders
 WHERE customer_id = :cust
 ORDER BY order_date DESC
 FETCH FIRST 20 ROWS ONLY;

-- S2 「상태별 일 매출」 — 운영 대시보드 · 상담원 화면 30 대가 1분마다 · 기간은 보통 한 달
SELECT TRUNC(order_date) AS dt, COUNT(*) AS cnt, SUM(total_amount) AS amt
  FROM orders
 WHERE status = :st
   AND order_date >= :d_from AND order_date < :d_to
 GROUP BY TRUNC(order_date);

-- S3 「주문 상세 헤더」 — 주문번호 단건
SELECT order_id, customer_id, order_date, status, total_amount
  FROM orders
 WHERE order_id = :id;

실행계획 학습용 예시 · 인덱스 규모와 적재 비용은 교재 18화 실측 ​

xplan
[S1 — 고객 390]
---------------------------------------------------------------------------------------------
| Id  | Operation                | Name   | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
---------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT         |        |      1 |        |     16 |00:00:00.01 |     193 |
|*  1 |  VIEW                    |        |      1 |      7 |     16 |00:00:00.01 |     193 |
|*  2 |   WINDOW SORT PUSHED RANK|        |      1 |      7 |     16 |00:00:00.01 |     193 |
|*  3 |    TABLE ACCESS FULL     | ORDERS |      1 |      7 |     16 |00:00:00.01 |     193 |
---------------------------------------------------------------------------------------------
   1 - filter("from$_subquery$_002"."rowlimit_$$_rownumber"<=20)
   2 - filter(ROW_NUMBER() OVER ( ORDER BY INTERNAL_FUNCTION("ORDER_DATE") DESC )<=20)
   3 - filter("CUSTOMER_ID"=:CUST)

[S2 — 2024년 6월 · 상태 하나]
----------------------------------------------------------------------------------------
| Id  | Operation           | Name   | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
----------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT    |        |      1 |        |     29 |00:00:00.01 |     193 |
|   1 |  HASH GROUP BY      |        |      1 |     94 |     29 |00:00:00.01 |     193 |
|*  2 |   FILTER            |        |      1 |        |     97 |00:00:00.01 |     193 |
|*  3 |    TABLE ACCESS FULL| ORDERS |      1 |     94 |     97 |00:00:00.01 |     193 |
----------------------------------------------------------------------------------------
   2 - filter(:D_FROM<:D_TO)
   3 - filter(("STATUS"=:ST AND "ORDER_DATE">=:D_FROM AND "ORDER_DATE"<:D_TO))

[S3]
-------------------------------------------------------------------------------------------------
| 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| ORDERS    |      1 |      1 |      1 |00:00:00.01 |       3 |
|*  2 |   INDEX UNIQUE SCAN         | PK_ORDERS |      1 |      1 |      1 |00:00:00.01 |       2 |
-------------------------------------------------------------------------------------------------
   2 - access("ORDER_ID"=:ID)

문제 ​

  1. 요청서의 인덱스 넷을 하나씩 만든다, 만들지 않는다, 다른 모양으로 만든다 중 하나로 판정하시오. 제안한 인덱스 세트로 S1, S2, S3의 실행계획이 어떻게 바뀌는지 줄 단위로 설명하시오.
  2. FK를 다시 켤 때 orders의 FK 컬럼에 인덱스가 없으면 휴면 고객 정리 배치에 무슨 일이 생기는지 설명하시오. 제안한 인덱스 세트가 그 문제를 어떻게 막는지도 설명하시오.
  3. 이 인덱스 세트의 적재 비용을 숫자로 적고, 그래도 만드는(또는 만들지 않는) 이유를 설명하시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자