테마
정산 「기간 주문 목록」 화면은 시작일과 종료일을 받아 최근 순으로 앞의 100건만 보여 준다('더 보기'는 없다). 기본값은 어제 하루이고, 정산팀은 분기 단위로도 연다. 하루 6,000번쯤 호출된다.
조민우가 SQL*Plus에서 재현해 보았다. 날짜를 VAR(VARCHAR2)로 받아 TO_DATE로 바꿔 넣고 어제 하루로 돌리니 920블록이었다. "이 정도면 문제없는데요?" 그런데 DBA가 보낸 Top SQL 목록에는 이 문장이 실행당 5,826블록으로 올라와 있다.
T_ORD_BIG: 1,000,000행 · 5,826블록 · 하루 약 913건 · 행은 ORD_DT 순서와 상관없이 흩어져 있다IX_ORD_BIG_DT (ORD_DT) · IX_ORD_BIG_DT_AMT (ORD_DT, ORD_AMT) · IX_ORD_BIG_DT_SEQ (ORD_DT, ORD_SEQ)java.time.LocalDate로 넘긴다(DATE 바인드). 바인드 피킹 켬 · 히스토그램은 ORD_ST에만 있다 · statistics_level = TYPICAL// PeriodOrderDao.java : 화면은 앞의 100건만 그린다 (setMaxRows)
String sql = "SELECT ORD_SEQ, CUS_ID, ORD_DT, ORD_ST, ORD_AMT FROM T_ORD_BIG "
+ "WHERE ORD_DT >= ? AND ORD_DT < ? ORDER BY ORD_DT DESC, ORD_SEQ DESC";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setObject(1, from); // LocalDate → DATE
ps.setObject(2, to.plusDays(1));
ps.setMaxRows(100);
-- 조민우의 재현 (SQL*Plus)
VAR d1 VARCHAR2(10)
VAR d2 VARCHAR2(10)
EXEC :d1 := '2024-06-16'; :d2 := '2024-06-17'
SELECT ORD_SEQ, CUS_ID, ORD_DT, ORD_ST, ORD_AMT FROM T_ORD_BIG
WHERE ORD_DT >= TO_DATE(:d1, 'YYYY-MM-DD') AND ORD_DT < TO_DATE(:d2, 'YYYY-MM-DD')
ORDER BY ORD_DT DESC, ORD_SEQ DESC;
[조민우의 재현 — 텍스트가 다른 새 문장 · 그가 넣은 하루로 태어났다]
---------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | Used-Mem |
---------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 913 |00:00:00.01 | 920 | |
| 1 | SORT ORDER BY | | 1 | 913 | 913 |00:00:00.01 | 920 |80896 (0)|
|* 2 | FILTER | | 1 | | 913 |00:00:00.01 | 920 | |
| 3 | TABLE ACCESS BY INDEX ROWID BATCHED| T_ORD_BIG | 1 | 913 | 913 |00:00:00.01 | 920 | |
|* 4 | INDEX RANGE SCAN | IX_ORD_BIG_DT | 1 | 913 | 913 |00:00:00.01 | 5 | |
---------------------------------------------------------------------------------------------------------------------
2 - filter(TO_DATE(:D2,'YYYY-MM-DD')>TO_DATE(:D1,'YYYY-MM-DD'))
4 - access("ORD_DT">=TO_DATE(:D1,'YYYY-MM-DD') AND "ORD_DT"<TO_DATE(:D2,'YYYY-MM-DD'))
[운영 커서 — V$SQL · 08:31에 태어났다]
CHILD PLAN_HASH EXECUTIONS BUFFER_GETS/EXEC IS_BIND_SENSITIVE IS_BIND_AWARE V$SQL_CS_HISTOGRAM
0 3305918526 4,388 5,826 Y N 0:0 1:4,388 2:0
[운영 커서 · 같은 텍스트와 DATE 바인드로 재현(분기 → 하루 순서) · 'ALLSTATS LAST +PEEKED_BINDS' · 하루 실행]
---------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | Used-Mem |
---------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 913 |00:00:00.21 | 5826 | |
| 1 | SORT ORDER BY | | 1 | 83103 | 913 |00:00:00.21 | 5826 |80896 (0)|
|* 2 | FILTER | | 1 | | 913 |00:00:00.21 | 5826 | |
|* 3 | TABLE ACCESS FULL | T_ORD_BIG | 1 | 83103 | 913 |00:00:00.20 | 5826 | |
---------------------------------------------------------------------------------------------------------------------
Peeked Binds (identified by position):
--------------------------------------
1 - :1 (DATE): 04/01/2024 00:00:00
2 - :2 (DATE): 07/01/2024 00:00:00
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter(:2>:1)
3 - filter(("ORD_DT">=:1 AND "ORD_DT"<:2))