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

공통 조회 엔진이 오전 DB CPU의 41%를 쓰는데, 엔진이 실행하는 조회는 모두 몇 블록짜리다

실전PLSQL유통590 / 700

상황 ​

유통 본사 레거시에는 2009년에 만든 「공통 조회 엔진」 PKG_QRY.RUN이 있다. 화면은 조회 번호와 파라미터 문자열을 넘기고, 엔진은 SQL 저장소 테이블 T_QRY_REPO에서 문장을 꺼내 DBMS_SQL로 파싱하고, 파라미터를 이름으로 바인드해 실행한 뒤 REF CURSOR로 돌려준다. SQL을 한곳에서 관리하고 감사하려고 만든 구조라 화면 40여 개가 이 엔진만 쓴다. 오전 10 ~ 11시 DB CPU의 41%가 이 엔진에서 나온다. 그런데 엔진이 실행하는 조회는 대부분 PK로 몇 블록을 읽는 문장이다. 개발팀은 엔진을 없애자고 하고 감사 담당은 지키자고 한다. 박팀장이 튜닝 쪽 의견을 숫자로 달라고 했다.

테이블과 인덱스 ​

  • T_QRY_REPO SQL 저장소(이 문제에서 쓰는 표): 312행 · QRY_ID PK · SQL_TXT · USE_YN · 감사 담당만 고칠 수 있다
  • 호출 상위 6개 조회가 전체 호출의 92%다. Q017 매장 재고 한 건(CF18 PK, 2블록) · Q021 상품 현재가(CF05 PK, 3블록) · Q030 영수증 머리(CF10_PK, 4블록) · Q031 영수증 상품(CF11_PK, 1 ~ 4행) · Q044 상품 한 건(CF01 PK) · Q052 발주 진행 상태(CF12 PK)
  • 화면 서버(Java)는 요청마다 {call PKG_QRY.RUN(?, ?, ?)}를 준비하고 닫는다(문장 캐시 없음). session_cached_cursors = 50 · DB 서버는 4코어

원본 SQL ​

sql
-- PKG_QRY.RUN (2009) : 저장소의 SQL을 꺼내 부를 때마다 파싱하고 실행해 REF CURSOR로 돌려준다
PROCEDURE RUN(p_qry_id IN VARCHAR2, p_params IN VARCHAR2, rc OUT SYS_REFCURSOR) IS
  v_sql VARCHAR2(4000);
  c     INTEGER;
  n     INTEGER;
BEGIN
  SELECT SQL_TXT INTO v_sql FROM T_QRY_REPO WHERE QRY_ID = p_qry_id AND USE_YN = 'Y';

  c := DBMS_SQL.OPEN_CURSOR;
  DBMS_SQL.PARSE(c, v_sql, DBMS_SQL.NATIVE);
  FOR p IN (SELECT REGEXP_SUBSTR(p_params, '[^;]+', 1, LEVEL) AS kv      -- 'shp=3;itm=1042;dt=20230601'
              FROM DUAL CONNECT BY LEVEL <= REGEXP_COUNT(p_params, ';') + 1) LOOP
    DBMS_SQL.BIND_VARIABLE(c, ':' || SUBSTR(p.kv, 1, INSTR(p.kv, '=') - 1),
                              SUBSTR(p.kv, INSTR(p.kv, '=') + 1));
  END LOOP;
  n  := DBMS_SQL.EXECUTE(c);
  rc := DBMS_SQL.TO_REFCURSOR(c);
END;

실행계획 학습용 예시 ​

xplan
[10 ~ 11시 · V$SYSSTAT 증가분 (인스턴스)]
  PKG_QRY.RUN 호출                    1,094,400
  execute count                       4,391,200   (호출당 4: 호출 블록 · 저장소 SELECT · 파라미터 분해 · 대상 SQL)
  parse count (total)                 2,190,300   (호출당 2: 호출 블록 · DBMS_SQL.PARSE)
  parse count (hard)                        118
  session cursor cache hits           2,104,900
  DB CPU                                1,980 초   (그중 PKG_QRY 계열 810초 · 41%)

[V$SQL · Q017 대상 SQL]
  SELECT INV_QTY FROM CF18 WHERE SHP_NO = :shp AND ITM_NO = :itm AND INV_BS_DT = :dt
  PARSE_CALLS 367,210 · EXECUTIONS 367,210 · 한 번 BUFFER_GETS 2 · 한 번 CPU 0.03 ms

[ASH · 10 ~ 11시 · 화면 서버 세션]
  CPU 74% · library cache: mutex X 9% · cursor: pin S 5%

[DBMS_HPROF · PKG_QRY.RUN 한 번 (Q017) · 평균]
  DBMS_SQL.PARSE                          0.21 ms
  파라미터 분해 (REGEXP + CONNECT BY)       0.18 ms
  그 밖의 PL/SQL                           0.12 ms
  DBMS_SQL.EXECUTE                        0.06 ms   (대상 SQL이 블록을 읽는 몫 0.03 ms 포함)
  DBMS_SQL.BIND_VARIABLE × 3              0.05 ms
  저장소 SELECT                            0.04 ms
  DBMS_SQL.TO_REFCURSOR                   0.03 ms
  합계                                    0.69 ms   (+ 호출 블록의 파싱과 실행 약 0.05 ms)

문제 ​

  1. 2블록짜리 조회 하나에 DB CPU가 0.7ms 넘게 드는 이유를 위 숫자로 설명하시오. 파스 호출이 엔진 호출의 두 배인 이유도 쓰시오.
  2. 감사 요구(SQL을 한곳에서 관리)를 지키면서 파스 호출과 CPU를 줄일 방법을 정하고 SQL로 쓰시오. 대안 셋(① 엔진이 파싱한 커서를 세션에 보관 ② 상위 조회를 정적 SQL 패키지로 ③ 화면이 SQL을 직접 실행)을 견주시오.
  3. 고른 안의 대가와, 엔진을 통째로 없애자는 개발팀 안을 평가하시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자