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

SQL은 한 줄도 안 바꿨는데 배포 다음 날부터 하드 파싱이 실행 수만큼 찍힌다

기초SELECT한빛유통551 / 700

상황 ​

한빛유통 개발팀은 지난주 화면 서버에 APM(성능 추적 도구)을 붙였다. 요청 하나가 어떤 SQL을 불렀는지 따라가려고, 데이터 접근 계층이 SQL을 보내기 직전에 요청마다 다른 추적 번호를 주석으로 앞에 붙이게 했다. 배포 다음 날부터 DB 서버(코어 4개)의 CPU 사용률이 12%에서 41%로 올랐다. 조민우가 말했다. "SQL은 한 줄도 안 바꿨어요. 바인드 변수도 그대로고요."

테이블과 인덱스 ​

  • T_ORD: 100,000행 · 896블록 · PK_T_ORD (ORD_SEQ)
  • T_ORD_DET: 200,000행 · 896블록 · PK_T_ORD_DET (ORD_SEQ, ORD_DET_NO) · 주문당 1~3건
  • 주문 상세 화면을 비롯한 화면 SQL은 모두 바인드 변수로 쓰여 있다. 주문 상세는 한 번에 7블록 안팎을 읽는다.
  • CURSOR_SHARING = EXACT · session_cached_cursors = 50

원본 SQL ​

sql
// SqlTraceInterceptor.java (지난주 배포) : SQL을 보내기 직전에 추적 번호를 주석으로 붙인다
public String beforePrepare(String sql) {
    String traceId = TraceContext.current().traceId();      // 요청마다 다르다 (16자리)
    return "/* trace_id=" + traceId + " */ " + sql;
}

-- 주문 상세 화면의 SQL (원래 문장 · 바꾸지 않았다)
SELECT o.ORD_SEQ, o.ORD_DT, o.ORD_ST, o.ORD_AMT, d.ITM_ID, d.ORD_QTY, d.UNT_PRC
  FROM T_ORD o, T_ORD_DET d
 WHERE o.ORD_SEQ = :1
   AND d.ORD_SEQ = o.ORD_SEQ

실행계획 학습용 예시 ​

xplan
[배포 전후 같은 시간대 한 시간 · V$SYSSTAT 증가분]
                                  배포 전        배포 후
  execute count                 3,602,000      3,598,400
  parse count (total)           3,571,300      3,569,900
  parse count (hard)                9,800      3,566,200
  parse time cpu (cs)               2,100        428,500
  CPU used by this session (cs)   168,400        593,600

[배포 후 V$SQL · 주문 상세 문장을 찾아본 것]
  SQL_ID          EXECUTIONS  SQL_TEXT (앞부분)
  5a1c9f0d2k7wq            1  /* trace_id=4bf92f3577b34da6 */ SELECT o.ORD_SEQ, o.ORD_DT, o.ORD_ST, …
  g83m0x2bn1v5s            1  /* trace_id=7d0e41a9c2f35b18 */ SELECT o.ORD_SEQ, o.ORD_DT, o.ORD_ST, …
  1qw7z4c9h0d2u            1  /* trace_id=e19a5b0c77d24f63 */ SELECT o.ORD_SEQ, o.ORD_DT, o.ORD_ST, …
  …

[한 번 실행의 계획 (배포 전후가 같다)]
------------------------------------------------------------------------------------------------------------
| Id  | Operation                            | Name         | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                     |              |      1 |        |      2 |00:00:00.01 |       7 |
|   1 |  NESTED LOOPS                        |              |      1 |      2 |      2 |00:00:00.01 |       7 |
|   2 |   TABLE ACCESS BY INDEX ROWID        | T_ORD        |      1 |      1 |      1 |00:00:00.01 |       3 |
|*  3 |    INDEX UNIQUE SCAN                 | PK_T_ORD     |      1 |      1 |      1 |00:00:00.01 |       2 |
|   4 |   TABLE ACCESS BY INDEX ROWID BATCHED| T_ORD_DET    |      1 |      2 |      2 |00:00:00.01 |       4 |
|*  5 |    INDEX RANGE SCAN                  | PK_T_ORD_DET |      1 |      2 |      2 |00:00:00.01 |       3 |
------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   3 - access("O"."ORD_SEQ"=:1)
   5 - access("D"."ORD_SEQ"=:1)

문제 ​

  1. SQL을 바꾸지 않았는데 하드 파싱이 실행 수만큼 늘어난 이유를 위 자료로 설명하시오.
  2. 추적 기능은 살리면서 문장이 공유되도록 고치시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자