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

EXPLAIN PLAN에는 NL 조인인데 실행 통계에는 주문상품을 통째로 읽은 흔적이 있다

기초SELECT버거639 / 700

상황 ​

본사 「주간 상품 판매」 리포트는 한 주의 주문을 상품별로 묶는다. 마케팅팀이 월요일마다 지난주를 넣어 열고, 한 번에 0.6초쯤 걸린다. 조민우가 EXPLAIN PLAN을 떠 보고 말했다. "주문 9천 건마다 주문상품 PK를 찾아가는 NL이에요. 해시 조인으로 바꾸게 힌트를 넣죠." 그런데 DBA가 보낸 V$SQL에는 이 SQL이 한 번에 55,523블록을 읽고 대부분이 디스크 읽기라고 나와 있다. NL로 9천 건을 찾아간 일량으로는 보이지 않는다.

테이블과 인덱스 ​

  • ORD: 1,000,000행 · 22,422블록 · ORD_DT VARCHAR2(8) · 하루 약 1,370건 · ORD_X01 (ORD_DT, ORD_HMS)(날짜순 적재, CF 22,223) · ORD_PK (ORD_NO)
  • ORD_ITEM: 2,472,717행 · 55,265블록 · 주문당 약 2.47줄 · ORD_ITEM_PK (ORD_NO, ITEM_ID)
  • 통계: ORD는 2013년 7월 1일에 모은 통계 그대로다(NUM_ROWS 749,520 · ORD_DT 최댓값 '20130630'). ORD_ITEM은 최신이다.
  • optimizer_adaptive_plans 기본값(TRUE)

원본 SQL ​

sql
-- 조민우 안 : 해시 조인으로 고정한다
SELECT /*+ LEADING(o i) USE_HASH(i) */
       i.ITEM_ID, SUM(i.ORD_ITEM_QTY) AS QTY, COUNT(*) AS LINE_CNT
  FROM ORD o, ORD_ITEM i
 WHERE o.ORD_DT BETWEEN '20131216' AND '20131222'
   AND i.ORD_NO = o.ORD_NO
 GROUP BY i.ITEM_ID

실행계획 학습용 예시 ​

xplan
[조민우의 EXPLAIN PLAN · 힌트 없는 원래 SQL]
-----------------------------------------------------------------------------
| Id  | Operation                              | Name        | Rows  | Cost  |
-----------------------------------------------------------------------------
|   0 | SELECT STATEMENT                       |             |    92 |  6243 |
|   1 |  HASH GROUP BY                         |             |    92 |  6243 |
|   2 |   NESTED LOOPS                         |             |  3384 |  6231 |
|   3 |    NESTED LOOPS                        |             |  3384 |  6231 |
|   4 |     TABLE ACCESS BY INDEX ROWID BATCHED| ORD         |  1370 |    38 |
|*  5 |      INDEX RANGE SCAN                  | ORD_X01     |  1370 |     7 |
|*  6 |     INDEX RANGE SCAN                   | ORD_ITEM_PK |     2 |     3 |
|   7 |    TABLE ACCESS BY INDEX ROWID         | ORD_ITEM    |     2 |     2 |
-----------------------------------------------------------------------------
Note
-----
   - this is an adaptive plan

[DBA가 뜬 실제 실행 · DISPLAY_CURSOR(FORMAT => 'ALLSTATS LAST +ADAPTIVE') · 힌트 없는 원래 SQL]
Plan hash value: 2961807453

-------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                               | Name        | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  |
-------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                        |             |      1 |        |     87 |00:00:00.61 |   55523 |  55265 |
|   1 |  HASH GROUP BY                          |             |      1 |     92 |     87 |00:00:00.61 |   55523 |  55265 |
|*  2 |   HASH JOIN                             |             |      1 |   3384 |  23690 |00:00:00.58 |   55523 |  55265 |
|-  3 |    NESTED LOOPS                         |             |        |   3384 |        |            |         |        |
|-  4 |     NESTED LOOPS                        |             |        |   3384 |        |            |         |        |
|-  5 |      STATISTICS COLLECTOR               |             |        |        |        |            |         |        |
|   6 |       TABLE ACCESS BY INDEX ROWID BATCHED| ORD        |      1 |   1370 |   9590 |00:00:00.01 |     253 |      0 |
|*  7 |        INDEX RANGE SCAN                 | ORD_X01     |      1 |   1370 |   9590 |00:00:00.01 |      38 |      0 |
|-* 8 |      INDEX RANGE SCAN                   | ORD_ITEM_PK |        |      2 |        |            |         |        |
|-  9 |     TABLE ACCESS BY INDEX ROWID         | ORD_ITEM    |        |      2 |        |            |         |        |
|  10 |    TABLE ACCESS FULL                    | ORD_ITEM    |      1 |      2 |   2472K|00:00:00.35 |   55270 |  55265 |
-------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - access("I"."ORD_NO"="O"."ORD_NO")
   7 - access("O"."ORD_DT">='20131216' AND "O"."ORD_DT"<='20131222')
   8 - access("I"."ORD_NO"="O"."ORD_NO")

Note
-----
   - this is an adaptive plan (rows marked '-' are inactive)

문제 ​

  1. EXPLAIN PLAN과 실제 실행의 계획이 다른 이유를 설명하시오. 실제로 어느 줄들이 돌았는지, 실행 중에 무엇이 조인 방법을 바꿨는지 쓰시오.
  2. 조민우의 USE_HASH 힌트가 필요한지 판단하고, 대신 할 일을 SQL로 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자