Skip to content
튜닝 연습조인 튜닝
JOIN-033

본사 BI 야간 배치의 2013년 집계 세 문장 중 하나가 TEMP를 쓰고, 셋 다 같은 주문 헤더를 읽는다

실전SELECT버거183 / 700

상황 ​

본사 BI팀의 야간 배치는 2013년 집계 세 문장(S1 상품별 판매 수량, S2 판매 수량 상위 10개 매장, S3 월별 상품 유형 판매)을 차례로 돌린다. 세 문장을 합쳐 12초 가까이 걸리고, S2를 돌릴 때마다 TEMP 경보가 울린다. S2의 힌트는 작년에 "상세가 더 많으니 상세부터 읽어야 한다"며 넣은 것이다. 오과장은 세 문장을 하나씩 보고 고칠 것과 그대로 둘 것을 숫자로 정해 달라고 했다.

테이블과 인덱스 ​

  • ORD 주문: 1,000,000행 · 22,422블록 · 2013년 500,218행 · SHOP_NO 200곳
  • ORD_ITEM 주문상품: 2,472,717행 · 55,265블록 · ORD_ITEM_PK (ORD_NO, ITEM_ID) · 2013년 주문의 상품 1,236,018줄 · FK ORD_ITEM.ORD_NO → ORD (ENABLE VALIDATE)
  • ORD_ITEM의 ORD_DT와 ORD_HMS는 POS가 주문을 넣을 때 헤더에서 복사하는 반정규화 컬럼이다. 지금 헤더와 다른 줄은 0건이지만, 이를 지키는 제약은 없다
  • ITEM 상품: 92행 · 5블록 · ITEM_TYPE_CD 4종 / SHOP 매장: 200행 · 5블록 · SHOP_NM · ADDR
  • 통계 최신 · pga_aggregate_target 512M · AUTO (이 시간대 해시 조인 하나의 work area는 약 100MB)

원본 SQL ​

sql
-- [S1] 상품별 판매 수량
SELECT t.ITEM_ID, t.ITEM_NM, SUM(i.ORD_ITEM_QTY) AS qty
  FROM ORD o, ORD_ITEM i, ITEM t
 WHERE o.ORD_DT BETWEEN '20130101' AND '20131231'
   AND i.ORD_NO  = o.ORD_NO
   AND t.ITEM_ID = i.ITEM_ID
 GROUP BY t.ITEM_ID, t.ITEM_NM;

-- [S2] 판매 수량 상위 10개 매장
SELECT *
  FROM (SELECT /*+ LEADING(i o) USE_HASH(o) */
               s.SHOP_NO, s.SHOP_NM, s.ADDR, SUM(i.ORD_ITEM_QTY) AS qty
          FROM ORD o, ORD_ITEM i, SHOP s
         WHERE o.ORD_DT BETWEEN '20130101' AND '20131231'
           AND i.ORD_NO  = o.ORD_NO
           AND s.SHOP_NO = o.SHOP_NO
         GROUP BY s.SHOP_NO, s.SHOP_NM, s.ADDR
         ORDER BY qty DESC)
 WHERE ROWNUM <= 10;

-- [S3] 월별 상품 유형 판매
SELECT SUBSTR(i.ORD_DT, 1, 6) AS ym, t.ITEM_TYPE_CD, SUM(i.ORD_ITEM_QTY) AS qty
  FROM ORD_ITEM i, ITEM t
 WHERE i.ORD_DT BETWEEN '20130101' AND '20131231'
   AND t.ITEM_ID = i.ITEM_ID
 GROUP BY SUBSTR(i.ORD_DT, 1, 6), t.ITEM_TYPE_CD;

실행계획 학습용 예시 ​

xplan
------------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                 | Name     | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads | Writes |  OMem |  1Mem | Used-Mem | Used-Tmp|
------------------------------------------------------------------------------------------------------------------------------------------------
[S1]
|   0 | SELECT STATEMENT          |          |      1 |        |     92 |00:00:02.87 |   77692 |     0 |      0 |       |       |          |         |
|   1 |  HASH GROUP BY            |          |      1 |     92 |     92 |00:00:02.87 |   77692 |     0 |      0 |  1186K|  1186K| 1223K (0)|         |
|*  2 |   HASH JOIN               |          |      1 |   1236K|   1236K|00:00:02.41 |   77692 |     0 |      0 |  1376K|  1376K| 1270K (0)|         |
|   3 |    TABLE ACCESS FULL      | ITEM     |      1 |     92 |     92 |00:00:00.01 |       5 |     0 |      0 |       |       |          |         |
|*  4 |    HASH JOIN              |          |      1 |   1236K|   1236K|00:00:01.98 |   77687 |     0 |      0 |    37M|  6418K|   39M (0)|         |
|*  5 |     TABLE ACCESS FULL     | ORD      |      1 |    500K|    500K|00:00:00.24 |   22422 |     0 |      0 |       |       |          |         |
|   6 |     TABLE ACCESS FULL     | ORD_ITEM |      1 |   2472K|   2472K|00:00:00.45 |   55265 |     0 |      0 |       |       |          |         |
[S2]
|   0 | SELECT STATEMENT          |          |      1 |        |     10 |00:00:07.71 |   77692 | 11203 |  11203 |       |       |          |         |
|*  1 |  COUNT STOPKEY            |          |      1 |        |     10 |00:00:07.71 |   77692 | 11203 |  11203 |       |       |          |         |
|   2 |   VIEW                    |          |      1 |    200 |     10 |00:00:07.71 |   77692 | 11203 |  11203 |       |       |          |         |
|*  3 |    SORT ORDER BY STOPKEY  |          |      1 |    200 |     10 |00:00:07.71 |   77692 | 11203 |  11203 |  2048 |  2048 | 2048  (0)|         |
|   4 |     HASH GROUP BY         |          |      1 |    200 |    200 |00:00:07.70 |   77692 | 11203 |  11203 |  1542K|  1542K| 1490K (0)|         |
|*  5 |      HASH JOIN            |          |      1 |   1236K|   1236K|00:00:07.38 |   77692 | 11203 |  11203 |  1421K|  1421K| 1290K (0)|         |
|   6 |       TABLE ACCESS FULL   | SHOP     |      1 |    200 |    200 |00:00:00.01 |       5 |     0 |      0 |       |       |          |         |
|*  7 |       HASH JOIN           |          |      1 |   1236K|   1236K|00:00:06.95 |   77687 | 11203 |  11203 |   176M|    12M|   98M (1)|      88M|
|   8 |        TABLE ACCESS FULL  | ORD_ITEM |      1 |   2472K|   2472K|00:00:00.58 |   55265 |     0 |      0 |       |       |          |         |
|*  9 |        TABLE ACCESS FULL  | ORD      |      1 |    500K|    500K|00:00:00.30 |   22422 |     0 |      0 |       |       |          |         |
[S3]
|   0 | SELECT STATEMENT          |          |      1 |        |     48 |00:00:01.12 |   55270 |     0 |      0 |       |       |          |         |
|   1 |  HASH GROUP BY            |          |      1 |     48 |     48 |00:00:01.12 |   55270 |     0 |      0 |  1186K|  1186K| 1210K (0)|         |
|*  2 |   HASH JOIN               |          |      1 |   1236K|   1236K|00:00:00.88 |   55270 |     0 |      0 |  1376K|  1376K| 1270K (0)|         |
|   3 |    TABLE ACCESS FULL      | ITEM     |      1 |     92 |     92 |00:00:00.01 |       5 |     0 |      0 |       |       |          |         |
|*  4 |    TABLE ACCESS FULL      | ORD_ITEM |      1 |   1236K|   1236K|00:00:00.47 |   55265 |     0 |      0 |       |       |          |         |
------------------------------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
  [S1] 2 - access("T"."ITEM_ID"="I"."ITEM_ID")
       4 - access("I"."ORD_NO"="O"."ORD_NO")
       5 - filter(("O"."ORD_DT">='20130101' AND "O"."ORD_DT"<='20131231'))
  [S2] 1 - filter(ROWNUM<=10)
       3 - filter(ROWNUM<=10)
       5 - access("S"."SHOP_NO"="O"."SHOP_NO")
       7 - access("I"."ORD_NO"="O"."ORD_NO")
       9 - filter(("O"."ORD_DT">='20130101' AND "O"."ORD_DT"<='20131231'))
  [S3] 2 - access("T"."ITEM_ID"="I"."ITEM_ID")
       4 - filter(("I"."ORD_DT">='20130101' AND "I"."ORD_DT"<='20131231'))

문제 ​

  1. 세 문장 각각의 일량과 문제(있다면)를 실행계획의 줄 번호와 숫자로 설명하시오.
  2. 고칠 문장은 개선 SQL을 작성하고, 그대로 둘 문장은 그 이유를 쓰시오. 조인을 빼는 개선에는 그 전제와 확인 방법을 함께 쓰시오.
  3. 고친 뒤 배치 전체의 Buffers와 시간, TEMP가 어떻게 바뀌는지 정리하시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자