Skip to content
튜닝 연습파싱과 바인드 변수
PARSE-018

엑셀 내려받기가 먼저 돈 날은 최근 주문 목록 첫 페이지가 느리다

기초SELECT한빛유통563 / 700

상황 ​

정산팀의 「최근 주문」 화면은 최신 주문 20건을 보여 주고, 같은 화면의 「엑셀 내려받기」 버튼은 최근 5만 건을 파일로 내린다. 개발팀은 두 기능이 같은 DAO 메서드를 쓰게 하고 건수만 인자로 넘겼다(화면 20, 엑셀 50,000). 화면은 하루 2만 번쯤 호출된다. 조민우가 말했다. "어제는 첫 페이지가 바로 떴는데 오늘은 한 박자 늦어요. 정산팀이 아침 8시 52분에 엑셀부터 내려받았다고 하더라고요." 밤사이 통계를 다시 모아서 이 문장의 커서는 오늘 아침에 새로 만들어졌다.

테이블과 인덱스 ​

  • T_ORD_BIG: 1,000,000행 · 5,826블록 · 행은 ORD_DT 순서와 상관없이 흩어져 있다(인덱스로 1,000건을 읽으면 테이블 블록도 1,000개 가까이 읽는다)
  • 인덱스 IX_ORD_BIG_DT_SEQ (ORD_DT, ORD_SEQ): 높이 3 · 리프 약 3,000블록
  • 통계 최신 · 히스토그램은 ORD_ST에만 있다 · CURSOR_SHARING = EXACT · 바인드 피킹 켬

원본 SQL ​

sql
// OrderDao.java : 화면과 엑셀이 같은 메서드를 쓴다 (화면 n = 20, 엑셀 n = 50000)
public List<OrderRow> recent(int n) throws SQLException {
    String sql = "SELECT ORD_SEQ, CUS_ID, ORD_DT, ORD_AMT "
               + "FROM (SELECT ORD_SEQ, CUS_ID, ORD_DT, ORD_AMT FROM T_ORD_BIG "
               + "ORDER BY ORD_DT DESC, ORD_SEQ DESC) WHERE ROWNUM <= ?";
    try (PreparedStatement ps = conn.prepareStatement(sql)) {
        ps.setInt(1, n);
        return readRows(ps.executeQuery());
    }
}

실행계획 학습용 예시 ​

xplan
[V$SQL — 오늘 09:30 · 커서는 08:52에 새로 만들어졌다]
 CHILD  PLAN_HASH    EXECUTIONS  BUFFER_GETS/EXEC  FIRST_LOAD_TIME
     0  2860115731        3,412             5,826  2024-06-17/08:52:07

[자식 0 · 화면(n = 20) 한 번 · FORMAT 'ALLSTATS LAST +PEEKED_BINDS']
Plan hash value: 2860115731

----------------------------------------------------------------------------------------------------------
| Id  | Operation                | Name      | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Used-Mem |
----------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT         |           |      1 |        |     20 |00:00:00.21 |    5826 |          |
|*  1 |  COUNT STOPKEY           |           |      1 |        |     20 |00:00:00.21 |    5826 |          |
|   2 |   VIEW                   |           |      1 |  50000 |     20 |00:00:00.21 |    5826 |          |
|*  3 |    SORT ORDER BY STOPKEY |           |      1 |   1000K|     20 |00:00:00.21 |    5826 | 4096  (0)|
|   4 |     TABLE ACCESS FULL    | T_ORD_BIG |      1 |   1000K|   1000K|00:00:00.05 |    5826 |          |
----------------------------------------------------------------------------------------------------------

Peeked Binds (identified by position):
--------------------------------------
   1 - :1 (NUMBER): 50000

Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter(ROWNUM<=:1)
   3 - filter(ROWNUM<=:1)

[어제 — 같은 문장 · 커서는 07:40에 화면(n = 20)이 처음 불렀다]
 PLAN_HASH 1127394853 · INDEX FULL SCAN DESCENDING (IX_ORD_BIG_DT_SEQ) + TABLE ACCESS BY INDEX ROWID
 화면 한 번 24블록 · 엑셀 한 번 50,158블록 (A-Rows 50,000)

문제 ​

  1. 화면이 20건만 달라고 하는데도 100만 행을 정렬하는 계획을 타는 이유를 위 자료로 설명하시오. 어제는 거꾸로 엑셀이 손해를 봤다는 점도 설명하시오.
  2. 누가 먼저 오든 화면과 엑셀이 각자 맞는 계획을 타도록 코드와 SQL을 고치시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자