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

주문은 한 칸만 읽는데 주문상품은 매번 24칸을 다 뒤진다

실전SELECT버거388 / 700

상황 ​

버거 본사는 주문상품도 주문과 같은 기준의 파티션 복사본 ORD_ITEM_RANGE로 옮겼다. 그 뒤 주문과 주문상품을 조인하는 세 SQL이 느려졌다. ① 「월간 매장별 메뉴 판매」는 월초에 본사가 수십 번 돌리고, ② POS 「영수증 상세」는 하루 약 6만 번, ③ 「매장 월 주문상품 내려받기」는 하루 약 400번 호출된다. 세 SQL 모두 ORD_RANGE 쪽은 한 칸만 연다. 아래 ①과 ③은 2013년 6월(③은 매장 SH0042), ②는 2013년 7월 주문 777777로 돌린 결과다.

테이블과 인덱스 ​

  • ORD_RANGE 주문(파티션 복사본, 교재 14화): 1,000,000행 · PARTITION BY RANGE (ORD_YM) 월별 25칸(P201201 ~ P201312과 빈 P_DEFAULT). 칸 하나는 FULL로 약 1,980블록이다. 한 달 약 41,000건이고, 매장 200개라서 매장 하나의 한 달 주문은 약 205건이다.
  • ORD_ITEM_RANGE 주문상품(파티션 복사본): ORD_ITEM 2,472,717행을 같은 기준(PARTITION BY RANGE (ORD_YM), 25칸)으로 나눴다. 칸 하나는 약 103,000행이고 FULL로 약 3,310블록(데이터 약 2,300블록과 빈 블록 약 1,006블록), 24칸 전체는 약 79,400블록이다. 주문 한 건에 상품은 약 2.5건이다.
  • 주문상품의 ORD_YM은 늘 그 주문의 ORD_YM과 같다(적재할 때 주문에서 가져왔다).
  • LOCAL 인덱스: ORD_RANGE_PK (ORD_NO, ORD_YM), ORD_ITEM_RANGE_PK (ORD_NO, ITEM_ID, ORD_YM). 두 인덱스 모두 칸마다 높이 2다.
  • 매달 1일 보관 기간이 지난 달을 두 테이블에서 DROP PARTITION으로 정리한다. 통계는 최신이다.

원본 SQL ​

sql
-- ① 월간 매장별 메뉴 판매
SELECT o.SHOP_NO, i.ITEM_ID, SUM(i.ORD_ITEM_QTY) AS qty
  FROM ORD_RANGE o, ORD_ITEM_RANGE i
 WHERE o.ORD_YM = :v_ym
   AND i.ORD_NO = o.ORD_NO
 GROUP BY o.SHOP_NO, i.ITEM_ID;

-- ② 영수증 상세
SELECT o.ORD_NO, o.ORD_DT, o.ORD_HMS, i.ITEM_ID, i.ORD_ITEM_QTY
  FROM ORD_RANGE o, ORD_ITEM_RANGE i
 WHERE o.ORD_NO = :ord_no
   AND o.ORD_YM = :ord_ym
   AND i.ORD_NO = o.ORD_NO;

-- ③ 매장 월 주문상품 내려받기
SELECT i.ORD_NO, i.ITEM_ID, i.ORD_ITEM_QTY
  FROM ORD_ITEM_RANGE i
 WHERE i.ORD_NO IN (SELECT o.ORD_NO
                      FROM ORD_RANGE o
                     WHERE o.ORD_YM = :v_ym
                       AND o.SHOP_NO = :v_shop);

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

xplan
[① 월간 매장별 메뉴 판매 — :v_ym = '201306']
------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                 | Name           | Starts | E-Rows | A-Rows |      A-Time | Buffers | Pstart | Pstop |
------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT          |                |      1 |        |    18K | 00:00:01.92 |   81396 |        |       |
|   1 |  HASH GROUP BY            |                |      1 |    18K |    18K | 00:00:01.92 |   81396 |        |       |
|*  2 |   HASH JOIN               |                |      1 |   103K |   102K | 00:00:01.74 |   81396 |        |       |
|   3 |    PARTITION RANGE SINGLE |                |      1 |    41K |    41K | 00:00:00.04 |    1980 |    KEY |   KEY |
|*  4 |     TABLE ACCESS FULL     | ORD_RANGE      |      1 |    41K |    41K | 00:00:00.03 |    1980 |    KEY |   KEY |
|   5 |    PARTITION RANGE ALL    |                |      1 |  2472K |  2472K | 00:00:00.95 |   79416 |      1 |    25 |
|   6 |     TABLE ACCESS FULL     | ORD_ITEM_RANGE |     24 |  2472K |  2472K | 00:00:00.71 |   79416 |      1 |    25 |
------------------------------------------------------------------------------------------------------------------------

   2 - access("I"."ORD_NO"="O"."ORD_NO")
   4 - filter("O"."ORD_YM"=:V_YM)

[② 영수증 상세 — :ord_no = 777777, :ord_ym = '201307']
--------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                            | Name              | Starts | E-Rows | A-Rows |      A-Time | Buffers | Pstart | Pstop |
--------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                     |                   |      1 |        |      3 | 00:00:00.01 |      55 |        |       |
|   1 |  NESTED LOOPS                        |                   |      1 |      2 |      3 | 00:00:00.01 |      55 |        |       |
|   2 |   PARTITION RANGE SINGLE             |                   |      1 |      1 |      1 | 00:00:00.01 |       3 |    KEY |   KEY |
|   3 |    TABLE ACCESS BY LOCAL INDEX ROWID | ORD_RANGE         |      1 |      1 |      1 | 00:00:00.01 |       3 |    KEY |   KEY |
|*  4 |     INDEX UNIQUE SCAN                | ORD_RANGE_PK      |      1 |      1 |      1 | 00:00:00.01 |       2 |    KEY |   KEY |
|   5 |   PARTITION RANGE ALL                |                   |      1 |      2 |      3 | 00:00:00.01 |      52 |      1 |    25 |
|   6 |    TABLE ACCESS BY LOCAL INDEX ROWID | ORD_ITEM_RANGE    |     24 |      2 |      3 | 00:00:00.01 |      52 |      1 |    25 |
|*  7 |     INDEX RANGE SCAN                 | ORD_ITEM_RANGE_PK |     24 |      2 |      3 | 00:00:00.01 |      49 |      1 |    25 |
--------------------------------------------------------------------------------------------------------------------------------------

   4 - access("O"."ORD_NO"=:ORD_NO AND "O"."ORD_YM"=:ORD_YM)
   7 - access("I"."ORD_NO"=:ORD_NO)

[③ 매장 월 주문상품 내려받기 — :v_ym = '201306', :v_shop = 'SH0042']
--------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                            | Name              | Starts | E-Rows | A-Rows |      A-Time | Buffers | Pstart | Pstop |
--------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                     |                   |      1 |        |    512 | 00:00:00.07 |   12348 |        |       |
|   1 |  NESTED LOOPS                        |                   |      1 |    513 |    512 | 00:00:00.07 |   12348 |        |       |
|   2 |   SORT UNIQUE                        |                   |      1 |    205 |    205 | 00:00:00.03 |    1980 |        |       |
|   3 |    PARTITION RANGE SINGLE            |                   |      1 |    205 |    205 | 00:00:00.03 |    1980 |    KEY |   KEY |
|*  4 |     TABLE ACCESS FULL                | ORD_RANGE         |      1 |    205 |    205 | 00:00:00.03 |    1980 |    KEY |   KEY |
|   5 |   PARTITION RANGE ALL                |                   |    205 |      3 |    512 | 00:00:00.04 |   10368 |      1 |    25 |
|   6 |    TABLE ACCESS BY LOCAL INDEX ROWID | ORD_ITEM_RANGE    |   4920 |      3 |    512 | 00:00:00.04 |   10368 |      1 |    25 |
|*  7 |     INDEX RANGE SCAN                 | ORD_ITEM_RANGE_PK |   4920 |      3 |    512 | 00:00:00.03 |    9856 |      1 |    25 |
--------------------------------------------------------------------------------------------------------------------------------------

   4 - filter(("O"."SHOP_NO"=:V_SHOP AND "O"."ORD_YM"=:V_YM))
   7 - access("I"."ORD_NO"="O"."ORD_NO")

문제 ​

  1. 세 SQL에서 ORD_ITEM_RANGE가 몇 칸을 여는지 Pstart, Pstop과 Starts로 쓰고, ORD_RANGE는 한 칸인데 주문상품 쪽은 칸을 고르지 못하는 까닭을 설명하시오.
  2. 결과가 바뀌지 않게 세 SQL을 고치고, SQL마다 Buffers가 어떻게 바뀌는지 어림하시오.
  3. SQL을 고치는 대신 ORD_ITEM_RANGE에 ORD_NO 하나짜리 GLOBAL 인덱스를 만드는 안과 비교해, 세 SQL에서 무엇이 달라지는지와 그 대가를 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자