테마
유통 본사 상황판의 「기존점 객수」 타일은 개점한 지 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블록 · 높이 3CF07 매장: 6행 · 4블록 · CF07_PK (SHP_NO) · 1000(본사)은 판매가 없다:v_from '20231201' · :v_to '20231231' · :v_base '20221201'(이번 달 1일의 1년 전) · 통계 최신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
[지금 · 힌트를 지운 뒤]
-------------------------------------------------------------------------------------------------------
| 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)
NO_UNNEST의 FILTER, 매장 마스터에서 기존점을 먼저 고르는 모양의 일량을 어림해 비교하고, SQL을 고치시오.