Skip to content
튜닝 연습옵티마이저와 통계
OPT-043

점주 앱 세 화면에 같은 힌트를 넣었는데 하나만 빨라졌고 하나는 상품에 따라 몇 초씩 걸린다

실전SELECT버거633 / 700

상황 ​

버거 개발팀은 무한 스크롤 목록 화면을 FIRST_ROWS(20) 힌트로 고친 사례(OPT-035)를 보고 "목록 화면에는 FIRST_ROWS(20)을 넣는다"는 규칙을 만들어 점주 앱의 세 화면에 같은 힌트를 넣었다. 결과는 제각각이었다. A 「주문 목록」은 빨라졌다. B 「상품별 최근 판매」는 잘 팔리는 상품을 고르면 빠른데, 단종 상품을 고르면 3초씩 걸린다. C 「이번 달 판매 상위 10개 상품」(본사 화면)은 힌트를 넣기 전과 똑같이 0.6초다. 박팀장은 규칙을 다시 쓰라고 했다. "어떤 화면에 그 힌트가 통하는지 기준을 주세요."

테이블과 인덱스 ​

  • ORD: 1,000,000행 · 22,422블록 · ORD_X01 (ORD_DT, ORD_HMS) · ORD_X02 (SHOP_NO, ORD_DT, ORD_HMS) · 한 달 약 41,700건
  • ORD_ITEM: 2,472,717행 · 55,265블록 · ORD_ITEM_PK (ORD_NO, ITEM_ID) · ORD_ITEM_X01 (ORD_DT, ORD_HMS) 리프 9,150블록 · 날짜순 적재(클러스터링 팩터 약 55,000) · 대부분 버퍼 캐시에 없다
  • 상품 92종 · 상품 하나에 평균 26,877줄(전체의 1.1%) · 단종 상품 하나는 2012년 6월까지만 팔렸다(4,100줄)
  • POS는 하루 주문 약 1,370건, 주문상품 약 3,390줄을 넣는다. ORD_ITEM.ORD_DT는 주문의 ORD_DT를 그대로 복사한 컬럼이다.

원본 SQL ​

sql
-- A 「주문 목록」 (매장, 최신순 · 무한 스크롤)
SELECT /*+ FIRST_ROWS(20) */ ORD_NO, ORD_DT, ORD_HMS
  FROM ORD
 WHERE SHOP_NO = :shop
 ORDER BY ORD_DT DESC, ORD_HMS DESC;

-- B 「상품별 최근 판매」 (상품을 골라 최신순 · 무한 스크롤)
SELECT /*+ FIRST_ROWS(20) */ ORD_NO, ORD_DT, ORD_HMS, ORD_ITEM_QTY
  FROM ORD_ITEM
 WHERE ITEM_ID = :item
 ORDER BY ORD_DT DESC, ORD_HMS DESC;

-- C 「이번 달 판매 상위 10개 상품」 (전 매장)
SELECT /*+ FIRST_ROWS(20) */ i.ITEM_ID, SUM(i.ORD_ITEM_QTY) AS qty
  FROM ORD o, ORD_ITEM i
 WHERE o.ORD_DT BETWEEN :d1 AND :d2
   AND i.ORD_NO = o.ORD_NO
 GROUP BY i.ITEM_ID
 ORDER BY qty DESC
 FETCH FIRST 10 ROWS ONLY;

실행계획 학습용 예시 ​

xplan
[요약 — 첫 화면 기준]
 A   INDEX RANGE SCAN DESCENDING ORD_X02 + 테이블 20행 · 23블록 · 0.01초
 B   잘 팔리는 상품 : INDEX FULL SCAN DESCENDING ORD_ITEM_X01 · 인덱스 1,840줄을 읽고 20행 · 52블록
     단종 상품     : 같은 계획 · 아래
 C   힌트 전과 같은 계획 : HASH GROUP BY ← HASH JOIN (ORD 한 달 · ORD_ITEM FULL) · 56,336블록 · 0.6초

[B — 단종 상품 · 첫 화면 20행]
----------------------------------------------------------------------------------------------------------
| Id  | Operation                   | Name         | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads |
----------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |              |      1 |        |     20 |00:00:03.12 |   48330 | 41870 |
|*  1 |  TABLE ACCESS BY INDEX ROWID| ORD_ITEM     |      1 |     20 |     20 |00:00:03.12 |   48330 | 41870 |
|   2 |   INDEX FULL SCAN DESCENDING| ORD_ITEM_X01 |      1 |   1840 |   1854K|00:00:00.58 |    6870 |  6020 |
----------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter("ITEM_ID"=:ITEM)

문제 ​

  1. 세 화면에서 같은 힌트가 다르게 작동한 이유를 각각 설명하시오. B가 상품에 따라 달라지는 이유는 옵티마이저가 어떤 가정을 했기 때문인지 쓰시오.
  2. 세 화면이 어떤 값에서도 빠르도록 인덱스와 SQL을 고치시오. 인덱스를 더하는 대가도 쓰시오.
  3. "목록 화면에는 FIRST_ROWS(20)"을 대신할 규칙을 한두 문장으로 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자