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

일 조회를 살리려고 붙인 힌트 때문에 행사 매출 화면의 월 조회가 0.4초를 넘긴다

실전SELECT유통265 / 700

상황 ​

본사 영업팀의 「행사 매출」 화면은 행사 상품 두 개 중 하나라도 든 영수증의 매장별 매출을 기간으로 보여 준다. 매장 담당자는 하루 단위로(하루 1,500번), 영업팀장은 한 달 단위로(하루 30번) 본다. 지난달 이 SQL이 월 조회로 먼저 파싱된 날, 일 조회까지 해시 세미 조인 계획을 쓰면서 느려졌다. 개발자가 서브쿼리에 NO_UNNEST를 붙이자 일 조회는 나아졌는데, 이번에는 월 조회가 0.4초를 넘긴다.

테이블과 인덱스 ​

  • CF10 판매: 500,000행 · 4,583블록 · CF10_PK (SLE_NO) · IX_CF10_SLE_DT (SLE_DT) (하루치가 약 14블록에 모여 있다) · 하루 영수증 706장, 한 달 21,180장
  • CF11 판매상품: 1,096,708행 · 8,204블록 · CF11_PK (SLE_NO, ITM_NO) (높이 3, 영수증 한 장의 줄은 한 블록에 모여 있다) · 영수증당 평균 2.2줄
  • 행사 상품 두 개(:v_i1, :v_i2)의 판매 줄은 전 기간 25,222줄 · 행사 상품이 든 영수증은 전체의 약 5%(하루 35장, 한 달 1,060장)
  • 아래 계획은 한 달(2023-06-01 ~ 06-30) 조회 · 통계 최신

원본 SQL ​

sql
SELECT a.SHP_NO, SUM(b.SLE_QTY * b.SLE_UPRC) AS amt, COUNT(*) AS line_cnt
  FROM CF10 a, CF11 b
 WHERE a.SLE_DT BETWEEN :v_from AND :v_to
   AND b.SLE_NO = a.SLE_NO
   AND EXISTS (SELECT /*+ NO_UNNEST */ 1
                 FROM CF11 x
                WHERE x.SLE_NO = a.SLE_NO
                  AND x.ITM_NO IN (:v_i1, :v_i2))
 GROUP BY a.SHP_NO

실행계획 학습용 예시 ​

xplan
-------------------------------------------------------------------------------------------------------------------
| Id  | Operation                                | Name           | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
-------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                         |                |      1 |        |      5 |00:00:00.43 |  148764 |
|   1 |  HASH GROUP BY                           |                |      1 |      5 |      5 |00:00:00.43 |  148764 |
|*  2 |   FILTER                                 |                |      1 |        |   2322 |00:00:00.43 |  148764 |
|   3 |    NESTED LOOPS                          |                |      1 |  46400 |  46384 |00:00:00.25 |   85224 |
|   4 |     NESTED LOOPS                         |                |      1 |  46400 |  46384 |00:00:00.17 |   64044 |
|   5 |      TABLE ACCESS BY INDEX ROWID BATCHED | CF10           |      1 |  21180 |  21180 |00:00:00.01 |     504 |
|*  6 |       INDEX RANGE SCAN                   | IX_CF10_SLE_DT |      1 |  21180 |  21180 |00:00:00.01 |      84 |
|*  7 |      INDEX RANGE SCAN                    | CF11_PK        |  21180 |      2 |  46384 |00:00:00.12 |   63540 |
|   8 |     TABLE ACCESS BY INDEX ROWID          | CF11           |  46384 |      2 |  46384 |00:00:00.06 |   21180 |
|*  9 |    INDEX RANGE SCAN                      | CF11_PK        |  21180 |      1 |   1060 |00:00:00.13 |   63540 |
-------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - filter( EXISTS (SELECT /*+ NO_UNNEST */ 0 FROM "CF11" "X" WHERE "X"."SLE_NO"=:B1 AND
              ("X"."ITM_NO"=:V_I1 OR "X"."ITM_NO"=:V_I2)))
   6 - access("A"."SLE_DT">=:V_FROM AND "A"."SLE_DT"<=:V_TO)
   7 - access("B"."SLE_NO"="A"."SLE_NO")
   9 - access("X"."SLE_NO"=:B1)
       filter(("X"."ITM_NO"=:V_I1 OR "X"."ITM_NO"=:V_I2))

문제 ​

  1. 실행계획에서 서브쿼리가 언제, 몇 번 실행되는지 짚고, 148,764블록이 어디서 나오는지 설명하시오.
  2. 일 조회와 월 조회 각각에 대해 네 가지 모양(조인 뒤 FILTER, PUSH_SUBQ로 먼저 거르기, 펼쳐서 NL 세미 조인, 펼쳐서 해시 세미 조인)의 일량을 어림해 비교하시오.
  3. 두 조회가 모두 빠르도록 고치고, 그 방법의 부작용을 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자