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

결제 대기 화면에 맞춰 인덱스를 만들었는데 FULL로 돌고, 한 번 열 때마다 recursive calls가 8만 번 찍힌다

중급SELECT한빛유통682 / 700

상황 ​

주문관리팀의 「최근 영업일 결제 대기」 화면은 최근 n영업일 동안 들어온 결제 대기 주문을 보여 준다. 담당자들이 하루 300번쯤 열고, 한 번에 0.9초가 걸린다. 지난주 이 화면을 위해 (ORD_ST, ORD_DT) 인덱스를 만들었지만 계획은 그대로다. 기준일은 회사 공통 함수 FN_BIZ_DAY_FROM이 영업일 달력을 읽어 구한다. 개발자는 "함수는 오른쪽 값에만 썼고 컬럼은 가공하지 않았다"라고 한다.

테이블과 인덱스 ​

  • T_ORD_BIG: 1,000,000행 · 5,826블록 · ORD_DT DATE(전부 자정, 2022-01-01 ~ 2024-12-31, 하루 약 912건) · ORD_ST PENDING 8%(80,000행)
  • 행은 주문일과 상관없는 순서로 쌓여 있어서 같은 날의 주문이 서로 다른 블록에 흩어져 있다
  • 인덱스 IX_ORD_BIG_ST_DT (ORD_ST, ORD_DT): 지난주 생성 · 리프 2,950블록 · 통계 최신(히스토그램 없음)
  • T_BIZ_CAL 영업일 달력(이 문제에서 쓰는 테이블): 1,096행 · 5블록 · CAL_DT DATE · BIZ_YN · PK (CAL_DT) · 함수 안 SQL은 호출마다 3블록
  • 이 문제의 오늘은 2024-12-31(화)이고, 최근 3영업일의 시작일은 2024-12-27(금)이다

원본 SQL ​

sql
CREATE OR REPLACE FUNCTION FN_BIZ_DAY_FROM(p_days NUMBER) RETURN DATE IS
  v_dt DATE;
BEGIN
  SELECT MIN(CAL_DT) INTO v_dt
    FROM (SELECT CAL_DT
            FROM T_BIZ_CAL
           WHERE CAL_DT <= TRUNC(SYSDATE)
             AND BIZ_YN = 'Y'
           ORDER BY CAL_DT DESC)
   WHERE ROWNUM <= p_days;
  RETURN v_dt;
END;
/

-- :days = 3
SELECT ORD_SEQ, CUS_ID, ORD_DT, ORD_AMT
  FROM T_ORD_BIG
 WHERE ORD_ST = 'PENDING'
   AND ORD_DT >= FN_BIZ_DAY_FROM(:days)
 ORDER BY ORD_DT DESC, ORD_SEQ DESC;

실행계획 학습용 예시 ​

xplan
[DISPLAY_CURSOR (ALLSTATS LAST) · :days = 3]
-------------------------------------------------------------------------------------------------------
| Id  | Operation          | Name      | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Used-Mem |
-------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |           |      1 |        |    367 |00:00:00.91 |    5826 |          |
|   1 |  SORT ORDER BY     |           |      1 |   4000 |    367 |00:00:00.91 |    5826 | 38912  (0)|
|*  2 |   TABLE ACCESS FULL| T_ORD_BIG |      1 |   4000 |    367 |00:00:00.91 |    5826 |          |
-------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - filter(("ORD_ST"='PENDING' AND "ORD_DT">="FN_BIZ_DAY_FROM"(:DAYS)))

[세션 통계: 화면 한 번]   recursive calls 80,012 · session logical reads 245,861
[V$SQL: 함수 안 문장 (dep=1) · 화면 한 번의 증가분]
  SELECT MIN(CAL_DT) FROM (SELECT CAL_DT FROM T_BIZ_CAL WHERE CAL_DT <= TRUNC(SYSDATE) …) WHERE ROWNUM <= :B1
  EXECUTIONS 80,000 · BUFFER_GETS 240,000 (한 번에 3블록)

문제 ​

  1. 인덱스가 있는데 FULL로 처리되고 함수 안 SQL이 8만 번 실행된 이유를 실행계획의 E-Rows와 Predicate Information으로 설명하시오.
  2. 기준일 규칙(FN_BIZ_DAY_FROM)은 그대로 쓰면서 인덱스 범위 스캔이 되도록 고치시오. 함수를 스칼라 서브쿼리로 감싸는 안과 견주어 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자