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

매장 다섯 곳의 「기존점 객수」 타일이 지난주부터 하루 논리 읽기 3위에 올라 있다

실전SELECT유통264 / 700

상황 ​

유통 본사 상황판의 「기존점 객수」 타일은 개점한 지 1년이 넘은 매장(기존점)만 골라 이번 달 영수증 수와 영업일 수를 보여 준다. 상황판 네 대가 1분마다 새로 고치고 점장 앱도 같은 SQL을 써서 하루 약 6,000번 돈다. 다음 달에는 여섯 번째 매장 1006(판교점)이 문을 연다. 지난주 SQL 점검 도구가 이 SQL의 NO_UNNEST를 "FILTER가 바깥 행마다 서브쿼리를 되풀이하게 만드는 힌트"로 표시했고, 개발자가 힌트를 지워 배포했다. 그 뒤로 이 SQL이 하루 논리 읽기 3위로 올라왔다. 아래는 2023년 12월 화면이다.

테이블과 인덱스 ​

  • CF10 판매: 500,000행 · 4,583블록 · SHP_NO 1001 ~ 1005(다섯 매장 모두 2022-01-01부터 판매가 있다) · 2023년 12월 20,864건 · 기준일(2022-12-01) 앞의 판매 229,434건
  • 인덱스: CF10_PK (SLE_NO) · CF10_X01 (SHP_NO, SLE_DT): 리프 약 1,500블록 · 높이 3
  • CF07 매장: 6행 · 4블록 · CF07_PK (SHP_NO) · 1000(본사)은 판매가 없다
  • 바인드: :v_from '20231201' · :v_to '20231231' · :v_base '20221201'(이번 달 1일의 1년 전) · 통계 최신

원본 SQL ​

sql
SELECT a.SHP_NO,
       COUNT(*)                 AS rcpt_cnt,
       COUNT(DISTINCT a.SLE_DT) AS sle_days
  FROM CF10 a
 WHERE a.SLE_DT BETWEEN :v_from AND :v_to
   AND EXISTS (SELECT 1                      -- 기존점 : 기준일 앞에 판매가 있는 매장
                 FROM CF10 p
                WHERE p.SHP_NO = a.SHP_NO
                  AND p.SLE_DT < :v_base)
 GROUP BY a.SHP_NO
 ORDER BY a.SHP_NO

실행계획 학습용 예시 · 매장별 판매 건수는 적재 CSV에서 센 값이다 ​

xplan
[지금 · 힌트를 지운 뒤]
-------------------------------------------------------------------------------------------------------
| Id  | Operation          | Name     | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Used-Mem |
-------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |          |      1 |        |      5 |00:00:00.09 |     781 |          |
|   1 |  SORT GROUP BY     |          |      1 |      5 |      5 |00:00:00.09 |     781 | 2048  (0)|
|*  2 |   HASH JOIN SEMI   |          |      1 |  20800 |  20864 |00:00:00.08 |     781 | 1795K (0)|
|*  3 |    INDEX SKIP SCAN | CF10_X01 |      1 |  20800 |  20864 |00:00:00.01 |      75 |          |
|*  4 |    INDEX SKIP SCAN | CF10_X01 |      1 |    229K|    229K|00:00:00.04 |     706 |          |
-------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - access("P"."SHP_NO"="A"."SHP_NO")
   3 - access("A"."SLE_DT">=:V_FROM AND "A"."SLE_DT"<=:V_TO)
       filter(("A"."SLE_DT">=:V_FROM AND "A"."SLE_DT"<=:V_TO))
   4 - access("P"."SLE_DT"<:V_BASE)
       filter("P"."SLE_DT"<:V_BASE)

[지난주까지 · 서브쿼리에 NO_UNNEST]
-------------------------------------------------------------------------------------------------------
| Id  | Operation          | Name     | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Used-Mem |
-------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |          |      1 |        |      5 |00:00:00.01 |      90 |          |
|   1 |  SORT GROUP BY     |          |      1 |      5 |      5 |00:00:00.01 |      90 | 2048  (0)|
|*  2 |   FILTER           |          |      1 |        |  20864 |00:00:00.01 |      90 |          |
|*  3 |    INDEX SKIP SCAN | CF10_X01 |      1 |  20800 |  20864 |00:00:00.01 |      75 |          |
|*  4 |    INDEX RANGE SCAN| CF10_X01 |      5 |      1 |      5 |00:00:00.01 |      15 |          |
-------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - filter( EXISTS (SELECT /*+ NO_UNNEST */ 0 FROM "CF10" "P" WHERE "P"."SHP_NO"=:B1 AND "P"."SLE_DT"<:V_BASE))
   3 - access("A"."SLE_DT">=:V_FROM AND "A"."SLE_DT"<=:V_TO)
       filter(("A"."SLE_DT">=:V_FROM AND "A"."SLE_DT"<=:V_TO))
   4 - access("P"."SHP_NO"=:B1 AND "P"."SLE_DT"<:V_BASE)

문제 ​

  1. 두 계획에서 서브쿼리 쪽의 일이 각각 몇 번, 몇 블록인지 줄 번호로 설명하시오. FILTER 계획에서 4번 줄의 Starts가 20,864가 아니라 5인 이유도 쓰시오.
  2. 지금 계획(펼친 뒤 해시 세미 조인), 펼친 뒤 NL 세미 조인, NO_UNNEST의 FILTER, 매장 마스터에서 기존점을 먼저 고르는 모양의 일량을 어림해 비교하고, SQL을 고치시오.
  3. 고른 방법의 부작용과, 이 판단이 뒤집히는 조건을 서술하시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자