테마
콜센터 「고객 상담」 화면은 위쪽에 고객의 올해 주문 요약(건수, 금액)을, 아래쪽에 올해 최근 주문 10건을 보여 준다. 개발자는 두 부분을 SQL 하나로 받으려고 공통 부분을 WITH 절로 빼 두었고, 요약은 10건의 줄마다 붙어 나오게 했다. 화면은 하루 4,000번 열리고 한 번에 0.7초가 걸린다.
DBA는 이 화면이 열릴 때마다 TEMP 사용량이 16MB씩 올라갔다 내려간다고 알려 왔다.
T_ORD_BIG: 1,000,000행 · 5,826블록 · 고객마다 500건(2,000행 간격으로 흩어져 있다) · 2024년 333,245건(고객당 약 167건)IX_ORD_BIG_CUS_DT (CUS_ID, ORD_DT): 이미 있다 · BLEVEL 2M_CUS: 2,000행 · 30블록 · PK_M_CUS (CUS_ID)CUS_000123으로 연 화면의 실행계획이다WITH v AS (
SELECT o.CUS_ID, o.ORD_SEQ, o.ORD_DT, o.ORD_ST, o.ORD_AMT, c.CUS_NM, c.CUS_GD
FROM T_ORD_BIG o, M_CUS c
WHERE c.CUS_ID = o.CUS_ID
AND o.ORD_DT >= DATE '2024-01-01'
AND o.ORD_DT < DATE '2025-01-01'
)
SELECT s.CUS_NM, s.CUS_GD, s.ORD_CNT, s.ORD_AMT,
d.ORD_SEQ, d.ORD_DT, d.ORD_ST, d.ORD_AMT AS AMT
FROM (SELECT MAX(CUS_NM) AS CUS_NM, MAX(CUS_GD) AS CUS_GD,
COUNT(*) AS ORD_CNT, SUM(ORD_AMT) AS ORD_AMT
FROM v
WHERE CUS_ID = :cus) s,
(SELECT *
FROM (SELECT ORD_SEQ, ORD_DT, ORD_ST, ORD_AMT
FROM v
WHERE CUS_ID = :cus
ORDER BY ORD_DT DESC, ORD_SEQ DESC)
WHERE ROWNUM <= 10) d
----------------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | Reads | Writes |
----------------------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 10 | 00:00:00.74 | 9969 | 4072 | 2036 |
| 1 | TEMP TABLE TRANSFORMATION | | 1 | | 10 | 00:00:00.74 | 9969 | 4072 | 2036 |
| 2 | LOAD AS SELECT (CURSOR DURATION MEMORY) | SYS_TEMP_0FD9D6624_2A1B3C | 1 | | 0 | 00:00:00.49 | 5889 | 0 | 2036 |
|* 3 | HASH JOIN | | 1 | 333K | 333K | 00:00:00.30 | 5856 | 0 | 0 |
| 4 | TABLE ACCESS FULL | M_CUS | 1 | 2000 | 2000 | 00:00:00.01 | 30 | 0 | 0 |
|* 5 | TABLE ACCESS FULL | T_ORD_BIG | 1 | 333K | 333K | 00:00:00.09 | 5826 | 0 | 0 |
| 6 | MERGE JOIN CARTESIAN | | 1 | 10 | 10 | 00:00:00.25 | 4080 | 4072 | 0 |
| 7 | VIEW | | 1 | 1 | 1 | 00:00:00.12 | 2040 | 2036 | 0 |
| 8 | SORT AGGREGATE | | 1 | 1 | 1 | 00:00:00.12 | 2040 | 2036 | 0 |
|* 9 | VIEW | | 1 | 333K | 167 | 00:00:00.12 | 2040 | 2036 | 0 |
| 10 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6624_2A1B3C | 1 | 333K | 333K | 00:00:00.08 | 2040 | 2036 | 0 |
| 11 | BUFFER SORT | | 1 | 10 | 10 | 00:00:00.13 | 2040 | 2036 | 0 |
| 12 | VIEW | | 1 | 10 | 10 | 00:00:00.13 | 2040 | 2036 | 0 |
|* 13 | COUNT STOPKEY | | 1 | | 10 | 00:00:00.13 | 2040 | 2036 | 0 |
| 14 | VIEW | | 1 | 333K | 10 | 00:00:00.13 | 2040 | 2036 | 0 |
|* 15 | SORT ORDER BY STOPKEY | | 1 | 333K | 10 | 00:00:00.13 | 2040 | 2036 | 0 |
|* 16 | VIEW | | 1 | 333K | 167 | 00:00:00.12 | 2040 | 2036 | 0 |
| 17 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6624_2A1B3C | 1 | 333K | 333K | 00:00:00.08 | 2040 | 2036 | 0 |
----------------------------------------------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - access("C"."CUS_ID"="O"."CUS_ID")
5 - filter(("O"."ORD_DT">=TO_DATE(' 2024-01-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss') AND
"O"."ORD_DT"<TO_DATE(' 2025-01-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss')))
9 - filter("CUS_ID"=:CUS)
13 - filter(ROWNUM<=10)
15 - filter(ROWNUM<=10)
16 - filter("CUS_ID"=:CUS)
CUS_ID = :cus)이 왜 인덱스로 쓰이지 못했는지도 쓰시오./*+ INLINE */ 힌트를 쓰는 안과 비교해 읽는 양을 쓰시오.