테마
경영지원팀의 「월 주문 리포트」 화면은 탭 세 개(R1 등급과 상태별, R2 일별 추이와 합계, R3 상태별 전월 대비)를 한꺼번에 띄운다. 평소에는 하루 10번 남짓 열지만 월말 마감 주간에는 팀장들이 하루 150번쯤 열고, 그 주에는 이 화면의 SQL 셋이 AWR의 SQL ordered by Gets 1 ~ 3위를 차지한다. 박팀장은 "세 SQL을 각각 고치고, 인덱스를 만들지 요약 테이블을 만들지도 정해 달라"라고 했다. 아래는 2024년 6월 리포트를 열었을 때의 실행계획이다.
T_ORD_BIG: 1,000,000행 · 5,826블록 · 이 문제에서는 인덱스가 없다 · ORD_DT DATE(전부 자정) · ORD_ST는 COMPLETE · PENDING · CANCEL 세 가지M_CUS: 2,000행 · 30블록 · CUS_GD A · B · C:d1, :d2), 전달 1일(:d0)을 DATE로 넘긴다-- R1 등급 · 상태별
SELECT c.CUS_GD, 'COMPLETE' AS ORD_ST, COUNT(*) AS CNT, SUM(o.ORD_AMT) AS AMT
FROM T_ORD_BIG o, M_CUS c
WHERE c.CUS_ID = o.CUS_ID AND o.ORD_DT >= :d1 AND o.ORD_DT < :d2 AND o.ORD_ST = 'COMPLETE'
GROUP BY c.CUS_GD
UNION ALL
SELECT c.CUS_GD, 'PENDING', COUNT(*), SUM(o.ORD_AMT)
FROM T_ORD_BIG o, M_CUS c
WHERE c.CUS_ID = o.CUS_ID AND o.ORD_DT >= :d1 AND o.ORD_DT < :d2 AND o.ORD_ST = 'PENDING'
GROUP BY c.CUS_GD
UNION ALL
SELECT c.CUS_GD, 'CANCEL', COUNT(*), SUM(o.ORD_AMT)
FROM T_ORD_BIG o, M_CUS c
WHERE c.CUS_ID = o.CUS_ID AND o.ORD_DT >= :d1 AND o.ORD_DT < :d2 AND o.ORD_ST = 'CANCEL'
GROUP BY c.CUS_GD;
-- R2 일별 추이와 합계 줄
SELECT TO_CHAR(ORD_DT, 'YYYY-MM-DD') AS DT, COUNT(*) AS CNT, SUM(ORD_AMT) AS AMT
FROM T_ORD_BIG
WHERE ORD_DT >= :d1 AND ORD_DT < :d2
GROUP BY ORD_DT
UNION ALL
SELECT '합계', COUNT(*), SUM(ORD_AMT)
FROM T_ORD_BIG
WHERE ORD_DT >= :d1 AND ORD_DT < :d2
ORDER BY 1;
-- R3 상태별 전월 대비
SELECT 'THIS' AS MON, ORD_ST, COUNT(*) AS CNT, SUM(ORD_AMT) AS AMT
FROM T_ORD_BIG
WHERE ORD_DT >= :d1 AND ORD_DT < :d2
GROUP BY ORD_ST
UNION
SELECT 'PREV', ORD_ST, COUNT(*), SUM(ORD_AMT)
FROM T_ORD_BIG
WHERE ORD_DT >= :d0 AND ORD_DT < :d1
GROUP BY ORD_ST;
[R1]
-----------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 9 | 00:00:00.33 | 17568 |
| 1 | UNION-ALL | | 1 | | 9 | 00:00:00.33 | 17568 |
| 2 | HASH GROUP BY | | 1 | 3 | 3 | 00:00:00.12 | 5856 |
|* 3 | HASH JOIN | | 1 | 24539 | 24651 | 00:00:00.11 | 5856 |
| 4 | TABLE ACCESS FULL | M_CUS | 1 | 2000 | 2000 | 00:00:00.01 | 30 |
|* 5 | TABLE ACCESS FULL | T_ORD_BIG | 1 | 24539 | 24651 | 00:00:00.10 | 5826 |
| 6 | HASH GROUP BY | | 1 | 3 | 3 | 00:00:00.11 | 5856 |
|* 7 | HASH JOIN | | 1 | 2181 | 2191 | 00:00:00.10 | 5856 |
| 8 | TABLE ACCESS FULL | M_CUS | 1 | 2000 | 2000 | 00:00:00.01 | 30 |
|* 9 | TABLE ACCESS FULL | T_ORD_BIG | 1 | 2181 | 2191 | 00:00:00.10 | 5826 |
| 10 | HASH GROUP BY | | 1 | 3 | 3 | 00:00:00.10 | 5856 |
|* 11 | HASH JOIN | | 1 | 545 | 548 | 00:00:00.10 | 5856 |
| 12 | TABLE ACCESS FULL | M_CUS | 1 | 2000 | 2000 | 00:00:00.01 | 30 |
|* 13 | TABLE ACCESS FULL | T_ORD_BIG | 1 | 545 | 548 | 00:00:00.10 | 5826 |
-----------------------------------------------------------------------------------------------
3 - access("C"."CUS_ID"="O"."CUS_ID")
5 - filter(("O"."ORD_ST"='COMPLETE' AND "O"."ORD_DT">=:D1 AND "O"."ORD_DT"<:D2))
(9번, 13번 줄은 상태만 PENDING, CANCEL)
[R2]
-----------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 31 | 00:00:00.21 | 11652 |
| 1 | SORT ORDER BY | | 1 | 31 | 31 | 00:00:00.21 | 11652 |
| 2 | UNION-ALL | | 1 | | 31 | 00:00:00.21 | 11652 |
| 3 | HASH GROUP BY | | 1 | 30 | 30 | 00:00:00.11 | 5826 |
|* 4 | TABLE ACCESS FULL | T_ORD_BIG | 1 | 27266 | 27390 | 00:00:00.10 | 5826 |
| 5 | SORT AGGREGATE | | 1 | 1 | 1 | 00:00:00.10 | 5826 |
|* 6 | TABLE ACCESS FULL | T_ORD_BIG | 1 | 27266 | 27390 | 00:00:00.10 | 5826 |
-----------------------------------------------------------------------------------------------
4 - filter(("ORD_DT">=:D1 AND "ORD_DT"<:D2))
6 - filter(("ORD_DT">=:D1 AND "ORD_DT"<:D2))
[R3]
-----------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 6 | 00:00:00.21 | 11652 |
| 1 | SORT UNIQUE | | 1 | 6 | 6 | 00:00:00.21 | 11652 |
| 2 | UNION-ALL | | 1 | | 6 | 00:00:00.21 | 11652 |
| 3 | HASH GROUP BY | | 1 | 3 | 3 | 00:00:00.11 | 5826 |
|* 4 | TABLE ACCESS FULL | T_ORD_BIG | 1 | 27266 | 27390 | 00:00:00.10 | 5826 |
| 5 | HASH GROUP BY | | 1 | 3 | 3 | 00:00:00.10 | 5826 |
|* 6 | TABLE ACCESS FULL | T_ORD_BIG | 1 | 28190 | 28301 | 00:00:00.10 | 5826 |
-----------------------------------------------------------------------------------------------
4 - filter(("ORD_DT">=:D1 AND "ORD_DT"<:D2))
6 - filter(("ORD_DT">=:D0 AND "ORD_DT"<:D1))
[화면 한 번] 17,568 + 11,652 + 11,652 = 40,872블록 · 월말 주간 하루 150번이면 약 613만 블록
T_ORD_BIG을 한 번만 읽도록 고치시오. R2의 합계 줄과 R3의 UNION을 어떻게 처리했는지 근거를 쓰시오.