테마
유통 본사 레거시에는 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 · 감사 담당만 고칠 수 있다CF18 PK, 2블록) · Q021 상품 현재가(CF05 PK, 3블록) · Q030 영수증 머리(CF10_PK, 4블록) · Q031 영수증 상품(CF11_PK, 1 ~ 4행) · Q044 상품 한 건(CF01 PK) · Q052 발주 진행 상태(CF12 PK){call PKG_QRY.RUN(?, ?, ?)}를 준비하고 닫는다(문장 캐시 없음). session_cached_cursors = 50 · DB 서버는 4코어-- 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;
[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)