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

요일별 시간대 주문 리포트가 같은 22,422블록을 읽는 일자별 리포트보다 세 배 느리다

중급SELECT버거660 / 700

상황 ​

본사 마케팅팀의 「요일별 시간대 주문 수」 리포트는 2년 치 주문 100만 건을 요일과 시간대로 묶는다. 1.7초가 걸리는데, 같은 주문을 일자별로 묶는 리포트는 0.5초다. DBA는 "둘 다 Buffers 22,422로 같은데 왜 세 배 차이가 나는지 모르겠다. recursive calls도 거의 없다"라고 한다. 요일 이름과 시간대 구분은 개발팀 공통 함수 두 개가 만든다. 두 함수 모두 테이블을 읽지 않는다.

테이블과 인덱스 ​

  • ORD 주문: 1,000,000행 · 22,422블록 · ORD_DT VARCHAR2(8) '20120101' ~ '20131231'(731일) · ORD_HMS VARCHAR2(6) 09시 ~ 22시 · 두 컬럼 모두 NULL 허용
  • 인덱스 ORD_X01 (ORD_DT, ORD_HMS) 리프 3,774블록: 두 컬럼이 NULL 허용이라 이 리포트는 테이블을 FULL로 읽는다
  • FN_DOW_NM, FN_HOUR_BAND: PL/SQL 계산만 하는 함수(SQL 없음) · 통계 최신

원본 SQL ​

sql
CREATE OR REPLACE FUNCTION FN_DOW_NM(p_ymd VARCHAR2) RETURN VARCHAR2 IS
BEGIN
  RETURN TO_CHAR(TO_DATE(p_ymd, 'YYYYMMDD'), 'DY', 'NLS_DATE_LANGUAGE=KOREAN');
END;
/

CREATE OR REPLACE FUNCTION FN_HOUR_BAND(p_hms VARCHAR2) RETURN VARCHAR2 IS
  v_hh PLS_INTEGER := TO_NUMBER(SUBSTR(p_hms, 1, 2));
BEGIN
  RETURN CASE WHEN v_hh < 11 THEN '1.오전'
              WHEN v_hh < 14 THEN '2.점심'
              WHEN v_hh < 17 THEN '3.오후'
              WHEN v_hh < 20 THEN '4.저녁'
              ELSE '5.야간' END;
END;
/

SELECT FN_DOW_NM(ORD_DT) AS dow, FN_HOUR_BAND(ORD_HMS) AS band, COUNT(*) AS ord_cnt
  FROM ORD
 GROUP BY FN_DOW_NM(ORD_DT), FN_HOUR_BAND(ORD_HMS)
 ORDER BY 1, 2;

실행계획 학습용 예시 ​

xplan
[DISPLAY_CURSOR (ALLSTATS LAST)]
---------------------------------------------------------------------------------------------------
| Id  | Operation           | Name | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Used-Mem |
---------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT    |      |      1 |        |     35 |00:00:01.74 |   22422 |          |
|   1 |  SORT GROUP BY      |      |      1 |   1000K|     35 |00:00:01.74 |   22422 | 6144  (0)|
|   2 |   TABLE ACCESS FULL | ORD  |      1 |   1000K|   1000K|00:00:00.11 |   22422 |          |
---------------------------------------------------------------------------------------------------

[V$SQL: 이 문장]       ELAPSED_TIME 1.74 초 · CPU_TIME 1.71 초 · PLSQL_EXEC_TIME 1.12 초 · BUFFER_GETS 22,422
[세션 통계]            recursive calls 3 · execute count 1
[비교: 일자별 리포트]  SELECT ORD_DT, COUNT(*) FROM ORD GROUP BY ORD_DT · 0.51 초 · Buffers 22,422 · PLSQL_EXEC_TIME 0

문제 ​

  1. 읽는 블록 수가 같은데 세 배 느린 이유를 설명하시오. recursive calls에 아무것도 잡히지 않는 이유와, 그 시간을 무엇으로 확인할 수 있는지도 쓰시오.
  2. 결과가 바뀌지 않게 고치시오. 함수에 DETERMINISTIC을 붙이거나 스칼라 서브쿼리로 감싸는 안이 여기서 효과가 있는지도 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자