테마
한빛유통은 분기마다 튜닝 리뷰를 연다. 이번에는 「주문 관리」 메뉴의 SQL 다섯 개가 올라왔다. 박팀장은 이렇게 말했다. "다섯 개 다 인덱스를 탄다는데 실행계획에 FULL SCAN, SKIP SCAN 같은 말이 섞여 있어 불안하다. 무엇을 고치고 무엇을 둘지 근거와 함께 정리해 와라." Q1은 하루 수백 번, Q2와 Q3는 하루 몇 번 내려받는 파일, Q4는 하루 수천 번, Q5는 하루 수백 번 호출된다.
T_ORD: 100,000행 · 896블록(블록당 약 112행) · ORD_SEQ 순으로 쌓였다 · ORD_DT DATE(시분초 있음, 2023 ~ 2024, 하루 약 137건) · 같은 날짜의 주문은 ORD_SEQ가 730씩 떨어져 테이블 전체에 흩어져 있다 · 2024-06-14 137건 · 2024년 6월 4,110건CUS_ID NOT NULL 2,000명(고객당 50건, 역시 흩어져 있다) · PAY_TP CARD · CASH · POINT가 1/3씩(하루 안에서도 셋이 고르게 있다)PK_T_ORD (ORD_SEQ): BLEVEL 1 · 리프 약 190블록 · CLUSTERING_FACTOR 896IX_T_ORD_CUS (CUS_ID): BLEVEL 1 · 리프 약 330블록 · CLUSTERING_FACTOR 약 100,000IX_T_ORD_TP_DT (PAY_TP, ORD_DT): BLEVEL 1 · 리프 약 330블록 · CLUSTERING_FACTOR 약 100,000 · 통계 최신-- Q1 「전체 주문 수」 (메뉴 머리말 · 하루 수백 번)
SELECT COUNT(*) FROM T_ORD;
-- Q2 「주문 목록 받기 · 주문번호순」 (하루 몇 번 · 10만 행 전부)
SELECT ORD_SEQ, CUS_ID, ORD_DT, ORD_AMT
FROM T_ORD
ORDER BY ORD_SEQ;
-- Q3 「주문 목록 받기 · 고객순」 (하루 몇 번 · 10만 행 전부 · 미리보기가 느리다고 힌트를 넣었다)
SELECT /*+ FIRST_ROWS(100) */ ORD_SEQ, CUS_ID, ORD_DT, ORD_AMT
FROM T_ORD
ORDER BY CUS_ID;
-- Q4 「결제수단별 일 건수」 (하루 수천 번)
SELECT PAY_TP, COUNT(*)
FROM T_ORD
WHERE ORD_DT >= TO_DATE(:v_dt, 'YYYYMMDD')
AND ORD_DT < TO_DATE(:v_dt, 'YYYYMMDD') + 1
GROUP BY PAY_TP;
-- Q5 「기간 주문 목록」 (하루 수백 번 · 보통 한 달 · 지난해 누군가 힌트를 넣었다)
SELECT /*+ INDEX_SS(o IX_T_ORD_TP_DT) */
o.ORD_SEQ, o.CUS_ID, o.ORD_DT, o.PAY_TP, o.ORD_AMT
FROM T_ORD o
WHERE o.ORD_DT >= TO_DATE(:v_from, 'YYYYMMDD')
AND o.ORD_DT < TO_DATE(:v_to, 'YYYYMMDD') + 1;
[Q1]
----------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.01 | 197 |
| 1 | SORT AGGREGATE | | 1 | 1 | 1 |00:00:00.01 | 197 |
| 2 | INDEX FAST FULL SCAN| PK_T_ORD | 1 | 100K| 100K|00:00:00.01 | 197 |
----------------------------------------------------------------------------------------------
[Q2]
---------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
---------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 100K|00:00:00.08 | 1087 |
| 1 | TABLE ACCESS BY INDEX ROWID| T_ORD | 1 | 100K| 100K|00:00:00.08 | 1087 |
| 2 | INDEX FULL SCAN | PK_T_ORD | 1 | 100K| 100K|00:00:00.02 | 191 |
---------------------------------------------------------------------------------------------------
[Q3]
-------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 100K|00:00:00.21 | 100K|
| 1 | TABLE ACCESS BY INDEX ROWID| T_ORD | 1 | 100K| 100K|00:00:00.21 | 100K|
| 2 | INDEX FULL SCAN | IX_T_ORD_CUS | 1 | 100K| 100K|00:00:00.02 | 331 |
-------------------------------------------------------------------------------------------------------
(Q3 0번 줄 Buffers 정확한 값 100,331)
[Q4 — 2024-06-14]
----------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
----------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 3 |00:00:00.01 | 8 |
| 1 | SORT GROUP BY NOSORT | | 1 | 3 | 3 |00:00:00.01 | 8 |
|* 2 | FILTER | | 1 | | 137 |00:00:00.01 | 8 |
|* 3 | INDEX SKIP SCAN | IX_T_ORD_TP_DT | 1 | 137 | 137 |00:00:00.01 | 8 |
----------------------------------------------------------------------------------------------------
2 - filter(TO_DATE(:V_DT,'YYYYMMDD')+1>TO_DATE(:V_DT,'YYYYMMDD'))
3 - access("ORD_DT">=TO_DATE(:V_DT,'YYYYMMDD') AND "ORD_DT"<TO_DATE(:V_DT,'YYYYMMDD')+1)
filter(("ORD_DT">=TO_DATE(:V_DT,'YYYYMMDD') AND "ORD_DT"<TO_DATE(:V_DT,'YYYYMMDD')+1))
[Q5 — 2024년 6월]
------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 4110 |00:00:00.03 | 4129 |
|* 1 | FILTER | | 1 | | 4110 |00:00:00.03 | 4129 |
| 2 | TABLE ACCESS BY INDEX ROWID BATCHED| T_ORD | 1 | 4110 | 4110 |00:00:00.03 | 4129 |
|* 3 | INDEX SKIP SCAN | IX_T_ORD_TP_DT | 1 | 4110 | 4110 |00:00:00.01 | 19 |
------------------------------------------------------------------------------------------------------------------
1 - filter(TO_DATE(:V_TO,'YYYYMMDD')+1>TO_DATE(:V_FROM,'YYYYMMDD'))
3 - access("O"."ORD_DT">=TO_DATE(:V_FROM,'YYYYMMDD') AND "O"."ORD_DT"<TO_DATE(:V_TO,'YYYYMMDD')+1)
filter(("O"."ORD_DT">=TO_DATE(:V_FROM,'YYYYMMDD') AND "O"."ORD_DT"<TO_DATE(:V_TO,'YYYYMMDD')+1))
[참고 — T_ORD FULL] 896