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

IN으로 바꿨을 뿐인데 평가 상품 TOP 5가 13배 느려졌다

중급SELECT한빛유통237 / 700

상황 ​

상품팀의 「평가가 있는 상품의 판매 TOP 5」는 08화의 Q3 rev01(EXISTS, 839블록)을 다른 팀이 가져다 쓴 것이다. 그 팀은 IN으로 바꾸면서 사내 공통 가이드의 한 줄, "IN 서브쿼리에는 NO_UNNEST를 붙인다 (2019.03 해시 세미 조인 TEMP 장애 뒤)"를 따랐다. 그 뒤로 답은 같은데 Buffers가 839 → 11,001로 늘었다.

테이블과 인덱스 ​

  • T_ORD_DET 주문상세: 200,000행 · 전체 스캔 807블록 · 상품 100종류 × 2,000줄 · 주문번호 순으로 쌓여 있다
  • T_ITM_EVL 상품평가: 5,000행 · 48블록 · PK_T_ITM_EVL (ITM_ID, EVL_LST_NO) (인덱스 32블록) · 상품마다 50건 · 100개 상품 전부에 평가가 있다
  • 통계 최신 · 히스토그램 없음

원본 SQL ​

sql
SELECT d.ITM_ID, SUM(d.ORD_QTY * d.UNT_PRC) AS amt, COUNT(*) AS cnt
  FROM T_ORD_DET d
 WHERE d.ITM_ID IN (SELECT /*+ NO_UNNEST */ e.ITM_ID FROM T_ITM_EVL e)
 GROUP BY d.ITM_ID
 ORDER BY amt DESC
 FETCH FIRST 5 ROWS ONLY

실행계획 학습용 예시 · 수치는 교재 08화 실측 ​

xplan
--------------------------------------------------------------------------------------------------
| Id  | Operation                 | Name         | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
--------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT          |              |      1 |        |      5 |00:00:00.09 |   11001 |
|*  1 |  VIEW                     |              |      1 |      5 |      5 |00:00:00.09 |   11001 |
|*  2 |   WINDOW SORT PUSHED RANK |              |      1 |    100 |      5 |00:00:00.09 |   11001 |
|   3 |    HASH GROUP BY          |              |      1 |    100 |    100 |00:00:00.09 |   11001 |
|*  4 |     FILTER                |              |      1 |        |    200K|00:00:00.08 |   11001 |
|   5 |      TABLE ACCESS FULL    | T_ORD_DET    |      1 |    200K|    200K|00:00:00.01 |     807 |
|*  6 |      INDEX RANGE SCAN     | PK_T_ITM_EVL |   5097 |      1 |   5097 |00:00:00.03 |   10194 |
--------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter("from$_subquery$_003"."rowlimit_$$_rownumber"<=5)
   2 - filter(ROW_NUMBER() OVER ( ORDER BY SUM("D"."ORD_QTY"*"D"."UNT_PRC") DESC )<=5)
   4 - filter( EXISTS (SELECT /*+ NO_UNNEST */ 0 FROM "T_ITM_EVL" "E" WHERE "E"."ITM_ID"=:B1))
   6 - access("E"."ITM_ID"=:B1)

문제 ​

  1. 4번과 6번 줄을 읽고 이 계획이 서브쿼리를 어떻게 실행했는지 설명하시오. Starts가 주문상세 행 수(20만)가 아니라 5,097인 이유도 설명하시오.
  2. 옵티마이저가 서브쿼리를 펼치도록 SQL을 고치시오. 같은 뜻을 조인으로 쓰려면 무엇을 조심해야 하는지(조인과 DISTINCT의 위치)도 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자