테마
가맹점주 앱 「최근 주문 20건」이 19c 업그레이드 뒤 스칼라 서브쿼리 Unnesting 때문에 느려진 일이 있었다(Buffers 104 → 60,310, SUBQ-011). DBA는 재발을 막겠다며 오늘 밤 ALTER SYSTEM SET "_optimizer_unnest_scalar_sq" = FALSE를 반영하겠다고 통보했다.
박팀장은 반영 전에 스칼라 서브쿼리를 쓰는 주요 SQL 셋을 보고 판단해 달라고 했다. 아래는 지금(기본값)과, 복사본 DB에서 파라미터를 끈 뒤 잰 숫자다.
ORD: 1,000,000행 · 22,422블록 · 날짜순으로 쌓여 있다 · ORD_X01 (ORD_DT, ORD_HMS) · ORD_X02 (SHOP_NO, ORD_DT, ORD_HMS) · 한 달 약 41,700건 · 매장마다 약 5,000건(한 달 약 208건)ORD_ITEM: 2,472,717행 · 55,265블록 · ORD_ITEM_PK (ORD_NO, ITEM_ID) · ORD_ITEM_X01 (ORD_DT, ORD_HMS) · 한 달 약 103,000줄 · 하루 약 3,400줄SHOP: 200행 · 5블록 · ITEM: 92행 · 5블록 · 둘 다 PK-- Q1 가맹점주 앱 「최근 주문 20건」 (하루 1만 번)
SELECT *
FROM (SELECT o.ORD_NO, o.ORD_DT, o.ORD_HMS,
(SELECT SUM(i.ORD_ITEM_QTY) FROM ORD_ITEM i WHERE i.ORD_NO = o.ORD_NO) AS qty
FROM ORD o
WHERE o.SHOP_NO = :shop
ORDER BY o.ORD_DT DESC, o.ORD_HMS DESC)
WHERE ROWNUM <= 20;
-- Q2 본사 「매장별 월 판매 수량」 (하루 20 번)
SELECT s.SHOP_NO, s.SHOP_NM,
(SELECT SUM(i.ORD_ITEM_QTY)
FROM ORD o, ORD_ITEM i
WHERE o.SHOP_NO = s.SHOP_NO
AND o.ORD_DT BETWEEN :d1 AND :d2
AND i.ORD_NO = o.ORD_NO) AS qty
FROM SHOP s
ORDER BY s.SHOP_NO;
-- Q3 「일 상품 판매 순위」 (하루 200 번)
SELECT t.ITEM_ID,
(SELECT m.ITEM_NM FROM ITEM m WHERE m.ITEM_ID = t.ITEM_ID) AS item_nm,
t.qty
FROM (SELECT ITEM_ID, SUM(ORD_ITEM_QTY) AS qty
FROM ORD_ITEM
WHERE ORD_DT = :d
GROUP BY ITEM_ID) t
ORDER BY t.qty DESC;
[세 SQL · 지금(기본값) ↔ 복사본에서 _optimizer_unnest_scalar_sq = FALSE]
지금 Buffers 끈 뒤 Buffers 하루 실행 하루 블록 (지금 → 끈 뒤)
Q1 60,310 104 10,000 약 6억 310만 → 104만
Q2 56,341 209,165 20 약 113만 → 418만
Q3 268 268 200 약 5만 4천 → 5만 4천 (상품명 서브쿼리 Starts 88 · 펼쳐지지 않는다)
[Q2 — 지금 (펼쳐짐)]
-------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 200 |00:00:01.21 | 56341 |
| 1 | SORT ORDER BY | | 1 | 200 | 200 |00:00:01.21 | 56341 |
|* 2 | HASH JOIN OUTER | | 1 | 200 | 200 |00:00:01.21 | 56341 |
| 3 | TABLE ACCESS FULL | SHOP | 1 | 200 | 200 |00:00:00.01 | 5 |
| 4 | VIEW | VW_SSQ_1 | 1 | 200 | 200 |00:00:01.20 | 56336 |
| 5 | HASH GROUP BY | | 1 | 200 | 200 |00:00:01.20 | 56336 |
|* 6 | HASH JOIN | | 1 | 104K| 103K|00:00:01.05 | 56336 |
| 7 | TABLE ACCESS BY INDEX ROWID BATCHED| ORD | 1 | 41700 | 41712 |00:00:00.02 | 1071 |
|* 8 | INDEX RANGE SCAN | ORD_X01 | 1 | 41700 | 41712 |00:00:00.01 | 139 |
| 9 | TABLE ACCESS FULL | ORD_ITEM | 1 | 2472K| 2472K|00:00:00.52 | 55265 |
-------------------------------------------------------------------------------------------------------
2 - access("ITEM_1"(+)="S"."SHOP_NO")
6 - access("I"."ORD_NO"="O"."ORD_NO")
8 - access("O"."ORD_DT">=:D1 AND "O"."ORD_DT"<=:D2)
[Q2 — 파라미터를 끈 뒤 (복사본)]
-------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 200 |00:00:00.92 | 209K|
| 1 | SORT AGGREGATE | | 200 | 1 | 200 |00:00:00.91 | 209K|
| 2 | NESTED LOOPS | | 200 | 520 | 103K|00:00:00.88 | 209K|
| 3 | NESTED LOOPS | | 200 | 520 | 103K|00:00:00.47 | 167K|
| 4 | TABLE ACCESS BY INDEX ROWID BATCHED| ORD | 200 | 208 | 41712 |00:00:00.12 | 42312 |
|* 5 | INDEX RANGE SCAN | ORD_X02 | 200 | 208 | 41712 |00:00:00.01 | 600 |
|* 6 | INDEX RANGE SCAN | ORD_ITEM_PK | 41712 | 2 | 103K|00:00:00.21 | 125K|
| 7 | TABLE ACCESS BY INDEX ROWID | ORD_ITEM | 103K| 1 | 103K|00:00:00.30 | 41712 |
| 8 | SORT ORDER BY | | 1 | 200 | 200 |00:00:00.92 | 209K|
| 9 | TABLE ACCESS FULL | SHOP | 1 | 200 | 200 |00:00:00.01 | 5 |
-------------------------------------------------------------------------------------------------------
5 - access("O"."SHOP_NO"=:B1 AND "O"."ORD_DT">=:D1 AND "O"."ORD_DT"<=:D2)
6 - access("I"."ORD_NO"="O"."ORD_NO")
(DBMS_XPLAN 이 줄여 쓴 값 — 125K = 125,136 · 167K = 167,448 · 209K = 209,160 / 209,165)