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

고객센터 고객 목록이 페이지를 넘길 때마다 반 박자씩 늦다

기초DDL한빛유통654 / 700

상황 ​

고객센터 「고객 목록」은 고객 20명씩 한 페이지로 보여 주고, 줄마다 최근 주문일을 붙인다. 상담원 30명이 하루 약 4,000페이지를 넘긴다. 상담원들은 "페이지를 넘길 때마다 반 박자씩 늦다"라고 하고, 개발팀은 "실행계획을 떠 보면 4블록이라 화면 SQL은 문제가 없다"라고 한다. DBA가 AWR에서 읽은 블록 수 1위 SQL을 보내왔다. SELECT MAX(ORD_DT) FROM T_ORD WHERE CUS_ID = :B1인데, 개발팀은 "그런 SQL은 소스에 없다"라고 답했다.

테이블과 인덱스 ​

  • M_CUS 고객: 2,000행 · 30블록 · PK_M_CUS (CUS_ID)
  • T_ORD 주문: 100,000행 · 896블록 · PK_T_ORD (ORD_SEQ)만 있다 · CUS_ID 인덱스 없음 · 고객 한 명에 평균 50건 · ORD_DT는 시분초가 있는 DATE
  • 함수 FN_LAST_ORD_DT는 2021년에 만들었고, 고객 목록과 고객 상세, 주문 등록 화면 세 곳이 쓴다.
  • 통계 최신

원본 SQL ​

sql
CREATE OR REPLACE FUNCTION FN_LAST_ORD_DT(p_cus_id VARCHAR2) RETURN DATE IS
  v_dt DATE;
BEGIN
  SELECT MAX(ORD_DT) INTO v_dt
    FROM T_ORD
   WHERE CUS_ID = p_cus_id;
  RETURN v_dt;
END;
/

-- 고객 목록 (한 페이지 20명 · 다음 페이지는 앞 페이지 마지막 고객 :last_cus_id 뒤부터)
SELECT v.CUS_ID, v.CUS_NM, v.CUS_GD,
       FN_LAST_ORD_DT(v.CUS_ID) AS last_ord_dt
  FROM (SELECT CUS_ID, CUS_NM, CUS_GD
          FROM M_CUS
         WHERE CUS_ID > :last_cus_id
         ORDER BY CUS_ID) v
 WHERE ROWNUM <= 20;

실행계획 학습용 예시 ​

xplan
[화면 SQL: DISPLAY_CURSOR (ALLSTATS LAST) · 한 페이지]
-------------------------------------------------------------------------------------------------
| Id  | Operation                     | Name     | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
-------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |          |      1 |        |     20 |00:00:00.15 |       4 |
|*  1 |  COUNT STOPKEY                |          |      1 |        |     20 |00:00:00.15 |       4 |
|   2 |   VIEW                        |          |      1 |     20 |     20 |00:00:00.01 |       4 |
|   3 |    TABLE ACCESS BY INDEX ROWID| M_CUS    |      1 |     20 |     20 |00:00:00.01 |       4 |
|*  4 |     INDEX RANGE SCAN          | PK_M_CUS |      1 |     20 |     20 |00:00:00.01 |       2 |
-------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter(ROWNUM<=20)
   4 - access("CUS_ID">:LAST_CUS_ID)

[V$SQL: 함수 안의 문장 (dep=1) · 하루 누적]
  SQL_TEXT                                           EXECUTIONS   ROWS_PROCESSED   BUFFER_GETS   ELAPSED
  SELECT MAX(ORD_DT) FROM T_ORD WHERE CUS_ID = :B1       81,240           81,240    72,791,040    566 초
  PROGRAM_ID → SQLP.FN_LAST_ORD_DT (PROGRAM_LINE# 4)

[그 문장의 계획: DISPLAY_CURSOR(sql_id => '그 문장', format => 'ALLSTATS LAST')]
------------------------------------------------------------------------------------
| Id  | Operation          | Name  | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |       |      1 |        |      1 |00:00:00.01 |     896 |
|   1 |  SORT AGGREGATE    |       |      1 |      1 |      1 |00:00:00.01 |     896 |
|*  2 |   TABLE ACCESS FULL| T_ORD |      1 |     50 |     50 |00:00:00.01 |     896 |
------------------------------------------------------------------------------------
   2 - filter("CUS_ID"=:B1)

문제 ​

  1. 화면 SQL의 계획은 4블록인데 페이지마다 늦어지는 이유를 위 자료로 설명하시오. 개발팀이 소스에서 그 SQL을 찾지 못한 이유도 쓰시오.
  2. 이 함수를 쓰는 세 화면이 모두 빨라지도록 고치시오. 함수를 조인으로 바꾸는 안과 견주고, 개선 뒤 한 페이지의 일량도 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자