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

감사팀의 6월 영수증 대장이 판매상품 110만 줄을 세 번 묶고 TEMP까지 쓴다

실전SELECT유통251 / 700

상황 ​

본사 감사팀의 「월 영수증 대장」은 한 달 치 영수증마다 판매번호, 일시, 매장명, 상품 줄 수, 판매 수량, 판매 금액을 보여 준다. 감사 기간에는 하루 수십 번 내려받는데, 2023년 6월 치(20,953건)에 4.2초가 걸리고 그때마다 TEMP 사용량 경보가 울린다. 개발자는 "칸마다 스칼라 서브쿼리를 하나씩 썼다. 19c가 알아서 펼쳐 줬다는데 왜 느린지 모르겠다"라고 했다. 박팀장은 서브쿼리마다 남길지 바꿀지 근거를 붙여 정리해 달라고 했다.

테이블과 인덱스 ​

  • CF10 판매: 500,000행 · 4,583블록 · CF10_PK (SLE_NO) · 2023년 6월 20,953건 · 판매가 있는 매장은 5곳(1001 ~ 1005)
  • CF11 판매상품: 1,096,708행 · 8,204블록 · 판매 50만 건에 1 ~ 4줄씩 · CF11_PK (SLE_NO, ITM_NO)(인덱스 4,213블록) · 2023년 6월 45,935줄 · 판매일자 순서로 쌓여 있다
  • 판매번호는 판매일자 8자리로 시작한다('20230601-0308'). 지금 데이터에서 SUBSTR(SLE_NO, 1, 8) <> SLE_DT인 판매는 0건이다
  • CF07 매장: 6행 · 4블록 · CF07_PK (SHP_NO)
  • 스칼라 서브쿼리 Unnesting은 켜져 있다(기본값) · PGA 작업 영역은 기본 설정 · 통계 최신

원본 SQL ​

sql
SELECT h.SLE_NO, h.SLE_DT, h.SLE_HMS,
       (SELECT f.SHP_NM FROM CF07 f WHERE f.SHP_NO = h.SHP_NO)                    AS shp_nm,
       (SELECT COUNT(*) FROM CF11 s WHERE s.SLE_NO = h.SLE_NO)                    AS line_cnt,
       (SELECT SUM(s.SLE_QTY) FROM CF11 s WHERE s.SLE_NO = h.SLE_NO)              AS qty,
       (SELECT SUM(s.SLE_QTY * s.SLE_UPRC) FROM CF11 s WHERE s.SLE_NO = h.SLE_NO) AS amt
  FROM CF10 h
 WHERE h.SLE_DT BETWEEN '20230601' AND '20230630'
 ORDER BY h.SLE_NO

실행계획 학습용 예시 · 건수와 금액은 적재 데이터 그대로 ​

xplan
-------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                    | Name     | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Used-Mem | Used-Tmp |
-------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |          |      1 |        |  20953 |00:00:04.21 |   25214 |          |          |
|   1 |  TABLE ACCESS BY INDEX ROWID | CF07     |      5 |      1 |      5 |00:00:00.01 |      10 |          |          |
|*  2 |   INDEX UNIQUE SCAN          | CF07_PK  |      5 |      1 |      5 |00:00:00.01 |       5 |          |          |
|   3 |  SORT ORDER BY               |          |      1 |  20953 |  20953 |00:00:04.21 |   25214 | 2958K (0)|          |
|*  4 |   HASH JOIN OUTER            |          |      1 |  20953 |  20953 |00:00:04.17 |   25204 | 3311K (0)|          |
|*  5 |    HASH JOIN OUTER           |          |      1 |  20953 |  20953 |00:00:02.80 |   17000 | 3296K (0)|          |
|*  6 |     HASH JOIN OUTER          |          |      1 |  20953 |  20953 |00:00:01.42 |    8796 | 3281K (0)|          |
|*  7 |      TABLE ACCESS FULL       | CF10     |      1 |  20953 |  20953 |00:00:00.04 |    4583 |          |          |
|   8 |      VIEW                    | VW_SSQ_1 |      1 |    500K|    500K|00:00:01.31 |    4213 |          |          |
|   9 |       HASH GROUP BY          |          |      1 |    500K|    500K|00:00:01.25 |    4213 |   31M (1)|     22M  |
|  10 |        INDEX FAST FULL SCAN  | CF11_PK  |      1 |   1096K|   1096K|00:00:00.20 |    4213 |          |          |
|  11 |     VIEW                     | VW_SSQ_2 |      1 |    500K|    500K|00:00:01.33 |    8204 |          |          |
|  12 |      HASH GROUP BY           |          |      1 |    500K|    500K|00:00:01.27 |    8204 |   33M (1)|     25M  |
|  13 |       TABLE ACCESS FULL      | CF11     |      1 |   1096K|   1096K|00:00:00.24 |    8204 |          |          |
|  14 |    VIEW                      | VW_SSQ_3 |      1 |    500K|    500K|00:00:01.32 |    8204 |          |          |
|  15 |     HASH GROUP BY            |          |      1 |    500K|    500K|00:00:01.26 |    8204 |   33M (1)|     25M  |
|  16 |      TABLE ACCESS FULL       | CF11     |      1 |   1096K|   1096K|00:00:00.24 |    8204 |          |          |
-------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - access("F"."SHP_NO"=:B1)
   4 - access("VW_SSQ_3"."ITEM_1"(+)="H"."SLE_NO")
   5 - access("VW_SSQ_2"."ITEM_1"(+)="H"."SLE_NO")
   6 - access("VW_SSQ_1"."ITEM_1"(+)="H"."SLE_NO")
   7 - filter(("H"."SLE_DT">='20230601' AND "H"."SLE_DT"<='20230630'))

문제 ​

  1. 네 스칼라 서브쿼리가 계획에서 각각 어떻게 처리됐는지(남은 것과 펼쳐진 것) 줄 번호와 Starts, Buffers로 설명하시오. 펼쳐진 쪽이 왜 6월 치가 아니라 판매상품 전체를 묶는지도 설명하시오.
  2. 스칼라 서브쿼리마다 유지할지, 조인으로 바꿀지, 하나로 합칠지 정하고 개선 SQL을 작성하시오.
  3. 세 집계 서브쿼리를 NO_UNNEST로 막는 안, 매장명까지 조인하는 안과 비교해 일량과 부작용(메모리와 TEMP, 판매번호 규칙에 기대는 범위 조건, 매장 테이블의 유일성)을 서술하시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자