Skip to content
튜닝 연습서브쿼리와 쿼리 변환
SUBQ-025

품질팀의 클레임 역추적 조회가 그날 주문 37건을 찾는 데 6초 걸린다

중급SELECT버거260 / 700

상황 ​

본사 품질팀은 매장에서 상품 클레임이 들어오면 「클레임 역추적」 화면에 날짜와 상품을 넣어, 그날 그 상품이 팔린 주문(매장, 시각)을 뽑는다. 클레임이 몰리는 날은 수십 번 돌리는데 한 번에 6초가 걸린다. 서브쿼리의 NO_UNNEST 옆에는 -- 서브쿼리 먼저 풀지 말 것 (2014.02 오과장)이라는 주석이 있다. 2014년에 이 SQL은 바깥에도 o.ORD_DT = :dt가 있었고, ORD_ITEM에는 PK 말고 인덱스가 없었다. 2019년 리팩터링에서 "날짜는 서브쿼리에 있으니 중복"이라며 바깥의 날짜 조건을 지웠다.

테이블과 인덱스 ​

  • ORD 주문: 1,000,000행 · 22,422블록 · ORD_PK (ORD_NO) (높이 3) · ORD_X01 (ORD_DT, ORD_HMS) · 하루 주문 약 1,370건
  • ORD_ITEM 주문상품: 2,472,717행 · 55,265블록 · ORD_ITEM_PK (ORD_NO, ITEM_ID) (높이 3) · ORD_ITEM_X01 (ORD_DT, ORD_HMS) (2016년 추가, 하루치가 약 76블록에 모여 있다) · ORD_DT는 주문 헤더의 날짜를 그대로 복사한 값이다
  • 상품 하나는 전 기간에 약 2만 7천 주문에 들어 있다 · 2013-05-14 주문상품 3,387줄 · 그중 상품 4(와일드쉬림프버거)가 든 주문 37건
  • 통계 최신

원본 SQL ​

sql
SELECT o.ORD_NO, o.ORD_HMS, o.SHOP_NO
  FROM ORD o
 WHERE o.ORD_NO IN (SELECT /*+ NO_UNNEST */ i.ORD_NO     -- 서브쿼리 먼저 풀지 말 것 (2014.02 오과장)
                      FROM ORD_ITEM i
                     WHERE i.ORD_DT  = :dt
                       AND i.ITEM_ID = :item)
 ORDER BY o.ORD_HMS

실행계획 학습용 예시 ​

xplan
---------------------------------------------------------------------------------------------------
| Id  | Operation                     | Name        | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
---------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |             |      1 |        |     37 |00:00:06.12 | 3049299 |
|   1 |  SORT ORDER BY                |             |      1 |      1 |     37 |00:00:06.12 | 3049299 |
|*  2 |   FILTER                      |             |      1 |        |     37 |00:00:06.12 | 3049299 |
|   3 |    TABLE ACCESS FULL          | ORD         |      1 |   1000K|   1000K|00:00:00.31 |   22422 |
|*  4 |    TABLE ACCESS BY INDEX ROWID| ORD_ITEM    |   1000K|      1 |     37 |00:00:05.21 | 3026877 |
|*  5 |     INDEX UNIQUE SCAN         | ORD_ITEM_PK |   1000K|      1 |  26877 |00:00:04.02 | 3000000 |
---------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - filter( EXISTS (SELECT /*+ NO_UNNEST */ 0 FROM "ORD_ITEM" "I" WHERE "I"."ORD_NO"=:B1
              AND "I"."ITEM_ID"=:ITEM AND "I"."ORD_DT"=:DT))
   4 - filter("I"."ORD_DT"=:DT)
   5 - access("I"."ORD_NO"=:B1 AND "I"."ITEM_ID"=:ITEM)

문제 ​

  1. 4번과 5번 줄의 Starts가 1000K인 이유를 설명하시오. 2014년에는 이 힌트가 왜 문제가 되지 않았는지도 설명하시오.
  2. SQL을 고치시오. 고친 계획에서 서브쿼리를 먼저 읽을 때 HASH UNIQUE가 왜 붙는지, 이 SQL을 조인으로 직접 쓴다면 무엇을 조심해야 하는지 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자