Skip to content
튜닝 연습소트 튜닝과 페이징
SORT-042

메뉴별 판매 매장 목록이 하루치는 같은 매장 이름을 되풀이하고, 한 달로 넓히면 ORA-01489로 멈춘다

실전SELECT버거347 / 700

상황 ​

본사 메뉴팀의 「메뉴별 판매 매장」 화면은 기간을 고르면 메뉴마다 그 기간에 그 메뉴를 판 매장 이름을 쉼표로 이어 한 칸에 보여 준다. 하루를 고르면 '강남구청점,교대점,교대점,…'처럼 같은 매장이 여러 번 나오고, 한 달을 고르면 ORA-01489: result of string concatenation is too long으로 멈춘다. 메뉴팀은 월 단위로 보려고 만든 화면이라 지금은 쓸 수 없다고 한다. 고칠 안이 셋 올라왔다. 개발자는 (가) LISTAGG(DISTINCT …)로 바꾸자고 하고, 운영 DBA는 (나) MAX_STRING_SIZE를 EXTENDED로 바꿔 문자열 한도를 32,767바이트로 늘리자고 한다. 조민우는 (다) 먼저 (메뉴, 매장)으로 줄인 뒤 이어 붙이고 ON OVERFLOW TRUNCATE를 덧붙이자고 한다. 박팀장은 셋을 결과, 일량, 부작용으로 견주어 하나를 고르라고 했다.

테이블과 인덱스 ​

  • ORD 주문: 1,000,000행 · 22,422블록 · ORD_NO · ORD_DT VARCHAR2(8) · SHOP_NO · 인덱스 ORD_X01 (ORD_DT, ORD_HMS) · 2013년 12월 주문 41,540건, 2013-12-30 하루 1,344건
  • ORD_ITEM 주문상품: 2,472,717행 · 55,265블록 · ORD_ITEM_PK (ORD_NO, ITEM_ID)(리프 약 5,520블록) · 12월 주문상품 103,251줄, 12월 30일 3,306줄
  • SHOP 매장: 200행 · 5블록 · SHOP_NM VARCHAR2(100) · 이름은 9 ~ 30바이트(평균 11.4바이트, 200곳을 쉼표로 다 이으면 약 2,500바이트) · 서로 다른 매장 SH0070과 SH0089의 이름이 둘 다 '여의도점'이다
  • ITEM 상품: 92행 · 5블록 · 12월에는 92개 메뉴가 모두 팔렸고, 메뉴마다 196 ~ 200곳의 매장에서 팔렸다
  • 이 DB의 MAX_STRING_SIZE는 STANDARD(SQL의 VARCHAR2 한도 4,000바이트)다. 통계는 최신이다.

원본 SQL ​

sql
SELECT i.ITEM_ID, m.ITEM_NM,
       LISTAGG(s.SHOP_NM, ',') WITHIN GROUP (ORDER BY s.SHOP_NM) AS SHOP_LIST
  FROM ORD o, ORD_ITEM i, SHOP s, ITEM m
 WHERE o.ORD_DT BETWEEN :d_from AND :d_to
   AND i.ORD_NO = o.ORD_NO
   AND s.SHOP_NO = o.SHOP_NO
   AND m.ITEM_ID = i.ITEM_ID
 GROUP BY i.ITEM_ID, m.ITEM_NM
 ORDER BY i.ITEM_ID

실행계획 학습용 예시 · 건수, 바이트, Buffers는 교재 환경 ora-burger에서 잰 값 ​

xplan
[하루 · :d_from = :d_to = '20131230']
------------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                                 | Name        | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |
------------------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                          |             |      1 |        |     92 |00:00:00.12 |    5676 |       |       |          |
|   1 |  SORT GROUP BY                            |             |      1 |   3381 |     92 |00:00:00.12 |    5676 |   372K|   372K|  330K (0)|
|*  2 |   FILTER                                  |             |      1 |        |   3306 |00:00:00.12 |    5676 |       |       |          |
|*  3 |    HASH JOIN                              |             |      1 |   3381 |   3306 |00:00:00.12 |    5676 |  1355K|  1355K| 1702K (0)|
|   4 |     TABLE ACCESS FULL                     | ITEM        |      1 |     92 |     92 |00:00:00.01 |       6 |       |       |          |
|*  5 |     HASH JOIN                             |             |      1 |   3381 |   3306 |00:00:00.12 |    5670 |  1506K|  1506K| 1364K (0)|
|*  6 |      HASH JOIN                            |             |      1 |   1370 |   1344 |00:00:00.01 |      46 |  1449K|  1449K| 1717K (0)|
|   7 |       TABLE ACCESS FULL                   | SHOP        |      1 |    200 |    200 |00:00:00.01 |       6 |       |       |          |
|   8 |       TABLE ACCESS BY INDEX ROWID BATCHED | ORD         |      1 |   1370 |   1344 |00:00:00.01 |      40 |       |       |          |
|*  9 |        INDEX RANGE SCAN                   | ORD_X01     |      1 |   1370 |   1344 |00:00:00.01 |       8 |       |       |          |
|  10 |      INDEX FAST FULL SCAN                 | ORD_ITEM_PK |      1 |   2472K|   2472K|00:00:00.16 |    5624 |       |       |          |
------------------------------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - filter(:D_FROM<=:D_TO)
   3 - access("M"."ITEM_ID"="I"."ITEM_ID")
   5 - access("I"."ORD_NO"="O"."ORD_NO")
   6 - access("S"."SHOP_NO"="O"."SHOP_NO")
   9 - access("O"."ORD_DT">=:D_FROM AND "O"."ORD_DT"<=:D_TO)

[화면 · 하루]  1 한우불고기버거 | 강남구청점,경복궁점,교대점,교대점,구로디지털단지점,구리점,…   (메뉴당 평균 36줄, 가장 긴 칸 636바이트)
[한 달 · 2013-12-01 ~ 12-31]  ORA-01489 · 3번 줄까지 103,251줄 · 메뉴당 약 1,120줄 × 약 12.4바이트 ≈ 1만 4천 바이트

문제 ​

  1. 하루치에서 같은 매장이 되풀이되는 까닭과 한 달에서 ORA-01489가 나는 까닭을 실행계획의 줄 수(A-Rows)와 바이트로 설명하시오.
  2. (가), (나), (다)를 결과, 일량, 부작용으로 비교하고 하나를 고르시오.
  3. 고른 안을 SQL로 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자