Skip to content
튜닝 연습파티션
PART-039

주문을 매장 200곳 × 24개월, 4,800조각으로 나누자는 안이 올라왔다

실전DDL버거414 / 700

상황 ​

버거 본사 IT가 분석 DB의 주문 사본을 다시 지으려 한다. 매장 관리자 화면이 가장 자주 도니 매장마다 월마다 조각을 따로 두자는 안 (나)가 올라왔다. "매장 화면이 조각 하나만 읽으니 제일 빠르다." 오과장은 월 한 단계 안 (가)와 매장 해시 안 (다)를 함께 비교해 보자고 했다. 분석 DB에는 지난해 월 정리 뒤 GLOBAL 인덱스가 UNUSABLE이 된 장애 이후로 "GLOBAL 인덱스를 두지 않는다"는 운영 규칙이 있다.

테이블과 인덱스 ​

  • 원본 ORD_LIST(교재 14화): 주문 1,000,000행 · PARTITION BY LIST (ORD_YM) 24칸 · 빈 테이블에 APPEND로 채워 칸 하나 FULL ≈ 1,980블록. ORD_RANDOM에서 적재해 행이 매장끼리 섞여 있다(매장 하나의 한 달 205건이 거의 모두 다른 블록에 있다).
  • 한 달 약 41,000행(CTAS로 지으면 약 935블록) · 매장 200곳 · 매장 하나의 한 달 약 205행, 2년 약 4,900행이다.
  • SQL 다섯: Q1 「매장 주문 내역」 SHOP_NO와 ORD_YM(하루 약 3,000번) · Q2 「월 매장 실적」 ORD_YM 전 매장(하루 약 30번) · Q3 「영수증 조회」 ORD_NO와 ORD_YM(하루 약 6만 번) · Q4 「월 정리」 24개월 지난 달을 들어낸다(월 1회) · Q5 「폐점 매장 정리」 폐점 매장의 주문을 지운다(한 해 몇 번)
  • (가) LIST (ORD_YM) 월 한 단계 + LOCAL PK (ORD_NO, ORD_YM) + LOCAL 인덱스 (SHOP_NO, ORD_DT, ORD_HMS)
  • (나) LIST (ORD_YM) × LIST (SHOP_NO) 매장마다 한 조각(200개와 DEFAULT) · 서브파티션 키가 없는 (ORD_NO, ORD_YM)은 LOCAL 유니크가 될 수 없어 LOCAL 일반 인덱스 (ORD_NO)를 둔다
  • (다) LIST (ORD_YM) × HASH (SHOP_NO) 8조각 · 인덱스는 (나)와 같다
  • 세 안 모두 CTAS와 STORAGE (INITIAL 64K)로 짓는다.

원본 SQL ​

sql
-- Q1 「매장 주문 내역」
SELECT ORD_NO, ORD_DT, ORD_HMS
  FROM ORD_LIST
 WHERE SHOP_NO = :v_shop
   AND ORD_YM  = :v_ym
 ORDER BY ORD_DT, ORD_HMS;

-- Q2 「월 매장 실적」
SELECT SHOP_NO, COUNT(*) AS ord_cnt
  FROM ORD_LIST
 WHERE ORD_YM = :v_ym
 GROUP BY SHOP_NO;

-- Q3 「영수증 조회」
SELECT ORD_NO, ORD_YM, ORD_DT, ORD_HMS, SHOP_NO
  FROM ORD_LIST
 WHERE ORD_NO = :ord_no
   AND ORD_YM = :ord_ym;

-- Q4 「월 정리」
ALTER TABLE ORD_LIST DROP PARTITION P201201;

-- Q5 「폐점 매장 정리」
DELETE FROM ORD_LIST WHERE SHOP_NO = :v_closed_shop;

실행계획 학습용 예시 · 교재 14화 규모 ​

xplan
[지금 ORD_LIST · Q1 · SH0042 · 201306]
----------------------------------------------------------------------------------------------------------------
| Id  | Operation               | Name     | Starts | E-Rows | A-Rows |      A-Time | Buffers | Pstart | Pstop |
----------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT        |          |      1 |        |    207 | 00:00:00.03 |    1980 |        |       |
|   1 |  SORT ORDER BY          |          |      1 |    205 |    207 | 00:00:00.03 |    1980 |        |       |
|   2 |   PARTITION LIST SINGLE |          |      1 |    205 |    207 | 00:00:00.03 |    1980 |    KEY |   KEY |
|*  3 |    TABLE ACCESS FULL    | ORD_LIST |      1 |    205 |    207 | 00:00:00.03 |    1980 |    KEY |   KEY |
----------------------------------------------------------------------------------------------------------------

   3 - filter(("SHOP_NO"=:V_SHOP AND "ORD_YM"=:V_YM))

[시험 DB · (나) · Q1]
--------------------------------------------------------------------------------------------------------------------
| Id  | Operation                | Name        | Starts | E-Rows | A-Rows |      A-Time | Buffers | Pstart | Pstop |
--------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT         |             |      1 |        |    207 | 00:00:00.01 |       6 |        |       |
|   1 |  SORT ORDER BY           |             |      1 |    205 |    207 | 00:00:00.01 |       6 |        |       |
|   2 |   PARTITION LIST SINGLE  |             |      1 |    205 |    207 | 00:00:00.01 |       6 |    KEY |   KEY |
|   3 |    PARTITION LIST SINGLE |             |      1 |    205 |    207 | 00:00:00.01 |       6 |    KEY |   KEY |
|   4 |     TABLE ACCESS FULL    | ORD_LIST_SS |      1 |    205 |    207 | 00:00:00.01 |       6 |    KEY |   KEY |
--------------------------------------------------------------------------------------------------------------------

[시험 DB · (나) · Q3 · 777777 · 201307]
--------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                                    | Name            | Starts | E-Rows | A-Rows |      A-Time | Buffers | Pstart | Pstop |
--------------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                             |                 |      1 |        |      1 | 00:00:00.01 |     201 |        |       |
|   1 |  PARTITION LIST SINGLE                       |                 |      1 |      1 |      1 | 00:00:00.01 |     201 |    KEY |   KEY |
|   2 |   PARTITION LIST ALL                         |                 |      1 |      1 |      1 | 00:00:00.01 |     201 |      1 |   201 |
|   3 |    TABLE ACCESS BY LOCAL INDEX ROWID BATCHED | ORD_LIST_SS     |    200 |      1 |      1 | 00:00:00.01 |     201 |    KEY |   KEY |
|*  4 |     INDEX RANGE SCAN                         | ORD_LIST_SS_X01 |    200 |      1 |      1 | 00:00:00.01 |     200 |    KEY |   KEY |
--------------------------------------------------------------------------------------------------------------------------------------------

   4 - access("ORD_NO"=:ORD_NO)

[시험 DB · 세그먼트]  (가) 24개 · (나) 4,800개 · (다) 192개

문제 ​

  1. 세 안을 Q1 ~ Q5에 대어 보고, 한 번 실행의 블록 수와 하루 합계로 비교하시오.
  2. 고른 안의 DDL(테이블과 인덱스)을 쓰고, Q4와 Q5를 어떻게 처리할지 쓰시오.
  3. (나)안이 가장 빠른 Q1을 버리는 근거와, 고른 안에서 Q1을 더 줄일 방법과 그 대가를 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자