테마
이 문제는 교재 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(order_id) 하나뿐(실행계획에는 PK_ORDERS로 적는다) · FK 39개 전부 DISABLED(FK_ORDERS_CUSTOMER_ID 포함)(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으로 늘어난다-- 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;
[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)