테마
운영팀의 「기간 주문 조회」 화면은 기간을 넣으면 최신 주문부터 50건을 보여 준다. 같은 화면의 「엑셀 다운로드」 버튼은 똑같은 SQL을 열고 끝까지 내려받는다. 화면은 하루 3만 번, 다운로드는 하루 40번쯤 호출된다.
한 달을 내려받으면 40초가 걸린다는 민원에, 지난주 한 개발자가 힌트를 USE_HASH와 FULL로 바꿨다. 다운로드는 6초가 됐지만 이번에는 화면이 누를 때마다 0.3초씩 걸려 상담원들이 불만이다. 아래는 힌트를 바꾸기 전, 2024년 6월로 조회한 두 경우의 실행계획이다.
T_ORD_BIG: 1,000,000행 · 5,826블록 · IX_ORD_BIG_DT_SEQ (ORD_DT, ORD_SEQ) (09화에서 만든 인덱스) · 한 달 27,390건 (인덱스 리프 약 91블록) · 같은 날짜의 주문은 테이블 전체에 흩어져 있고, 한 달치 블록은 대부분 버퍼 캐시에 없다M_CUS: 2,000행 · 30블록 · PK_M_CUS (CUS_ID)SELECT /*+ LEADING(o c) USE_NL(c) INDEX_DESC(o IX_ORD_BIG_DT_SEQ) */
o.ORD_DT, o.ORD_SEQ, o.CUS_ID, c.CUS_NM, c.CUS_GD, o.ORD_ST, o.ORD_AMT
FROM T_ORD_BIG o, M_CUS c
WHERE o.ORD_DT >= :v_from
AND o.ORD_DT < :v_to
AND c.CUS_ID = o.CUS_ID
ORDER BY o.ORD_DT DESC, o.ORD_SEQ DESC
[화면: 첫 50행을 받고 커서를 닫았을 때]
------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | Reads |
------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 50 |00:00:00.01 | 106 | 0 |
|* 1 | FILTER | | 1 | | 50 |00:00:00.01 | 106 | 0 |
| 2 | NESTED LOOPS | | 1 | 27390 | 50 |00:00:00.01 | 106 | 0 |
| 3 | NESTED LOOPS | | 1 | 27390 | 50 |00:00:00.01 | 56 | 0 |
| 4 | TABLE ACCESS BY INDEX ROWID | T_ORD_BIG | 1 | 27390 | 50 |00:00:00.01 | 53 | 0 |
|* 5 | INDEX RANGE SCAN DESCENDING | IX_ORD_BIG_DT_SEQ | 1 | 27390 | 50 |00:00:00.01 | 3 | 0 |
|* 6 | INDEX UNIQUE SCAN | PK_M_CUS | 50 | 1 | 50 |00:00:00.01 | 3 | 0 |
| 7 | TABLE ACCESS BY INDEX ROWID | M_CUS | 50 | 1 | 50 |00:00:00.01 | 50 | 0 |
------------------------------------------------------------------------------------------------------------------------
[다운로드: 같은 계획으로 27,390행을 끝까지 받았을 때]
------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | Reads |
------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 27390 |00:00:38.21 | 55459 | 26982 |
|* 1 | FILTER | | 1 | | 27390 |00:00:38.21 | 55459 | 26982 |
| 2 | NESTED LOOPS | | 1 | 27390 | 27390 |00:00:38.19 | 55459 | 26982 |
| 3 | NESTED LOOPS | | 1 | 27390 | 27390 |00:00:37.52 | 28069 | 26982 |
| 4 | TABLE ACCESS BY INDEX ROWID | T_ORD_BIG | 1 | 27390 | 27390 |00:00:37.43 | 27481 | 26982 |
|* 5 | INDEX RANGE SCAN DESCENDING | IX_ORD_BIG_DT_SEQ | 1 | 27390 | 27390 |00:00:00.01 | 91 | 0 |
|* 6 | INDEX UNIQUE SCAN | PK_M_CUS | 27390 | 1 | 27390 |00:00:00.05 | 588 | 0 |
| 7 | TABLE ACCESS BY INDEX ROWID | M_CUS | 27390 | 1 | 27390 |00:00:00.58 | 27390 | 0 |
------------------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter(:V_FROM<:V_TO)
5 - access("O"."ORD_DT"<:V_TO AND "O"."ORD_DT">=:V_FROM)
6 - access("C"."CUS_ID"="O"."CUS_ID")
A-Rows와 Buffers, Reads를 비교해, 같은 계획이 화면에서는 빠르고 다운로드에서는 느린 이유를 부분범위 처리로 설명하시오.FIRST_ROWS(n) 힌트, 커버링 인덱스, fetch size 늘리기)을 비교하고 부작용을 서술하시오.