테마
지난주 DBA팀이 "대량 리포트는 인덱스 랜덤 액세스를 피한다"는 기준으로 리포트 SQL 40여 개를 일괄 점검했다. 주문상세를 읽는 SQL에는 FULL(d) 힌트를 더했고, 기존 힌트는 건드리지 않았다.
그다음 날부터 영업팀의 「일 상품 판매 리포트」가 1.4초씩 걸린다. 이 리포트는 날짜 하나를 고르면 그날 주문의 상품별 판매 수량과 금액을 낸다. 영업 담당 30명이 날짜를 바꿔 가며 하루 500번쯤 돌린다. 아래는 2024-06-14(주문 137건)의 실행계획이다.
T_ORD 주문: 100,000행 · 896블록 · IX_T_ORD_DT (ORD_DT) · 하루 약 137건(같은 날 주문은 테이블 곳곳에 흩어져 있다)T_ORD_DET 주문상세: 200,000행 · 896블록 · PK_T_ORD_DET (ORD_SEQ, ORD_DET_NO) (BLEVEL 1) · 주문당 1 ~ 3건(평균 2)M_ITM 상품: 100행 · 5블록 · PK_M_ITM (ITM_ID)SELECT /*+ LEADING(o d m) USE_NL(d) USE_NL(m) FULL(d) */
m.ITM_ID, m.ITM_NM, SUM(d.ORD_QTY) AS qty, SUM(d.ORD_QTY * d.UNT_PRC) AS amt
FROM T_ORD o, T_ORD_DET d, M_ITM m
WHERE o.ORD_DT >= TO_DATE(:v_dt, 'YYYYMMDD')
AND o.ORD_DT < TO_DATE(:v_dt, 'YYYYMMDD') + 1
AND d.ORD_SEQ = o.ORD_SEQ
AND m.ITM_ID = d.ITM_ID
GROUP BY m.ITM_ID, m.ITM_NM
-----------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | Used-Mem |
-----------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 96 |00:00:01.38 | 123168 | |
| 1 | HASH GROUP BY | | 1 | 100 | 96 |00:00:01.38 | 123168 | 1290K (0)|
| 2 | NESTED LOOPS | | 1 | 274 | 275 |00:00:01.38 | 123168 | |
| 3 | NESTED LOOPS | | 1 | 274 | 275 |00:00:01.37 | 122893 | |
| 4 | NESTED LOOPS | | 1 | 274 | 275 |00:00:01.37 | 122891 | |
| 5 | TABLE ACCESS BY INDEX ROWID BATCHED| T_ORD | 1 | 137 | 137 |00:00:00.01 | 139 | |
|* 6 | INDEX RANGE SCAN | IX_T_ORD_DT | 1 | 137 | 137 |00:00:00.01 | 2 | |
|* 7 | TABLE ACCESS FULL | T_ORD_DET | 137 | 2 | 275 |00:00:01.36 | 122752 | |
|* 8 | INDEX UNIQUE SCAN | PK_M_ITM | 275 | 1 | 275 |00:00:00.01 | 2 | |
| 9 | TABLE ACCESS BY INDEX ROWID | M_ITM | 275 | 1 | 275 |00:00:00.01 | 275 | |
-----------------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
6 - access("O"."ORD_DT">=TO_DATE(:V_DT,'YYYYMMDD') AND "O"."ORD_DT"<TO_DATE(:V_DT,'YYYYMMDD')+1)
7 - filter("D"."ORD_SEQ"="O"."ORD_SEQ")
8 - access("M"."ITM_ID"="D"."ITM_ID")
Hint Report (identified by operation id / Query Block Name / Object Alias):
Total hints for statement: 4
---------------------------------------------------------------------------
1 - SEL$1
- LEADING(o d m)
7 - SEL$1 / "D"@"SEL$1"
- FULL(d)
- USE_NL(d)
8 - SEL$1 / "M"@"SEL$1"
- USE_NL(m)
Starts와 Predicate Information으로 지금 계획이 무엇을 하는지 설명하고, 힌트 네 개가 어떻게 겹쳐서 이 계획이 됐는지 쓰시오.FULL(d)를 꼭 살려야 한다면 함께 바꿔야 할 힌트는 무엇이며, 그 계획의 일량은 얼마인지 쓰시오.