Skip to content
튜닝 연습PL/SQL과 사용자 정의 함수
PLSQL-035

조건 네 칸짜리 주문 검색 하나가 공유 풀에 하루 2만 개 넘는 문장을 남긴다

중급PLSQL한빛유통685 / 700

상황 ​

쇼핑몰 관리 화면의 「주문 검색」은 고객번호, 주문 상태, 주문일 시작과 끝 네 칸 중 채운 칸만 조건으로 쓴다. 프로시저 P_ORD_SEARCH가 조건을 이어 붙여 동적 SQL을 만들고 결과를 SYS_REFCURSOR로 돌려준다. 작년에 바인드 변수로 바꾸려다 "채운 칸마다 바인드 개수가 달라 USING을 쓸 수 없다"(ORA-01008)며 되돌렸다. DBA는 이 프로시저의 문장이 하루 2만 개 넘게 하드 파싱되어 공유 풀을 밀어낸다고 한다.

테이블과 인덱스 ​

  • T_ORD 주문: 100,000행 · 896블록 · ORD_DT DATE(시분초 있음, 2023 ~ 2024, 하루 약 137건) · ORD_ST COMPLETE 90% · PENDING 8% · CANCEL 2% · 고객마다 50건
  • 인덱스: PK_T_ORD (ORD_SEQ) · IX_T_ORD_CUS_DT (CUS_ID, ORD_DT) · IX_T_ORD_DT (ORD_DT) · 통계 최신
  • 화면은 적어도 한 칸은 채워야 검색할 수 있다 · CURSOR_SHARING = EXACT

원본 SQL ​

sql
PROCEDURE P_ORD_SEARCH(p_cus_id IN VARCHAR2, p_ord_st IN VARCHAR2,
                       p_dt_fr  IN DATE,     p_dt_to  IN DATE,
                       o_rc     OUT SYS_REFCURSOR) IS
  v_sql VARCHAR2(4000) := 'SELECT ORD_SEQ, CUS_ID, ORD_DT, ORD_ST, ORD_AMT FROM T_ORD WHERE 1 = 1';
BEGIN
  IF p_cus_id IS NOT NULL THEN
    v_sql := v_sql || ' AND CUS_ID = ''' || p_cus_id || '''';
  END IF;
  IF p_ord_st IS NOT NULL THEN
    v_sql := v_sql || ' AND ORD_ST = ''' || p_ord_st || '''';
  END IF;
  IF p_dt_fr IS NOT NULL THEN
    v_sql := v_sql || ' AND ORD_DT >= TO_DATE(''' || TO_CHAR(p_dt_fr, 'YYYYMMDD') || ''', ''YYYYMMDD'')';
  END IF;
  IF p_dt_to IS NOT NULL THEN
    v_sql := v_sql || ' AND ORD_DT < TO_DATE(''' || TO_CHAR(p_dt_to, 'YYYYMMDD') || ''', ''YYYYMMDD'') + 1';
  END IF;
  OPEN o_rc FOR v_sql || ' ORDER BY ORD_DT DESC';
END;

실행계획 학습용 예시 ​

xplan
[V$SQLAREA: P_ORD_SEARCH가 만든 문장 · 하루 (FORCE_MATCHING_SIGNATURE로 묶음)]
  채운 칸                       문장 수     실행 수    하드 파싱
  고객                            6,912       9,840       6,912
  고객 + 기간                     8,105       8,330       8,105
  상태 + 기간                     3,288       3,420       3,288
  기간                            2,617       2,790       2,617
  그 밖의 조합 다섯 가지            940         990         940
  합계 (아홉 가지 조합)          21,862      25,370      21,862

[세션 통계 · 하루 합]  parse count (hard) 21,862 · parse time cpu 1,940 cs

[한 번의 실행 · 고객만 채움]
-------------------------------------------------------------------------------------------------------
| Id  | Operation                    | Name            | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
-------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |                 |      1 |        |     50 |00:00:00.01 |      53 |
|   1 |  TABLE ACCESS BY INDEX ROWID | T_ORD           |      1 |     50 |     50 |00:00:00.01 |      53 |
|*  2 |   INDEX RANGE SCAN DESCENDING| IX_T_ORD_CUS_DT |      1 |     50 |     50 |00:00:00.01 |       3 |
-------------------------------------------------------------------------------------------------------
   2 - access("CUS_ID"='CUS_000123')

문제 ​

  1. 문장이 하루 2만 개 넘게 생기는 이유와, 작년에 USING으로 바꾸다 실패한 이유를 설명하시오.
  2. 채운 칸만 조건으로 쓰는 구조는 그대로 두고 모든 값을 바인드 변수로 넘기도록 고치시오. 고친 뒤 문장이 최대 몇 개가 되는지 쓰시오.
  3. 동적 SQL 대신 (:v IS NULL OR 컬럼 = :v) 모양의 정적 SQL 하나로 쓰는 안과 견주어 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자