Skip to content
튜닝 연습옵티마이저와 통계
OPT-058

튜닝 팩을 들여온 다음 주부터 MD의 상품별 매장 판매 화면이 이틀에 하루꼴로 하루 종일 느리다

실전PLSQL유통648 / 700

상황 ​

본사 유통 MD 화면 「상품별 매장 판매」는 상품 번호를 넣으면 매장별 판매 수량과 금액을 보여 준다. 화면은 패키지 프로시저를 부르고, 프로시저 안의 정적 SQL이 상품 번호를 바인드로 받는다. MD 20명이 하루 약 900번 열고, 그 가운데 약 360번이 판매 1위와 2위 상품(1001, 1002)이다. 3월에 아침 첫 조회의 값에 따라 하루 종일 계획이 갈리는 사고가 난 뒤, DBA는 이 SQL을 해시 조인 계획 하나만 받아들인 베이스라인으로 지켜 왔다. 10월 6일 본사가 Tuning Pack을 계약했고, DBA는 그날 2019년부터 꺼 두었던 자동 SQL 튜닝 작업을 다시 켰다. 10월 7일부터 이틀에 하루꼴로 인기 상품 조회가 하루 종일 2초씩 걸린다. 박팀장은 지금 할 일과 다시 생기지 않게 할 일을 함께 정하자고 했다.

테이블과 인덱스 ​

  • CF11 판매상품: 지금 1,118,268행 · 8,365블록(올해 신상품 판매가 더해졌다) · IX_CF11_ITM (ITM_NO): BLEVEL 2 · 리프 약 2,200 · ITM_NO에 FREQUENCY 히스토그램
  • CF11은 매일 01:00 적재 뒤 통계를 다시 모은다(no_invalidate => FALSE). 그래서 이 SQL은 아침 첫 조회 때 하드 파싱한다.
  • 상품별 판매 줄은 크게 치우쳐 있다: 1001 275,135 · 1002 202,156 · 1003 30,870 · 기존 상품 가운데 가장 적은 1031 3,032 · 올해 신상품 1109 217 · 1096 140
  • CF10 판매: 지금 509,800행 · 4,673블록 · CF10_PK (SLE_NO): BLEVEL 2 · 판매하는 매장 5곳
  • 운영 DB: Enterprise Edition · optimizer_use_sql_plan_baselines TRUE · 자동 캡처는 꺼져 있다
  • SYS_AUTO_SPM_EVOLVE_TASK의 설정은 한 번도 바꾼 적이 없다

원본 SQL ​

sql
-- 화면이 부르는 프로시저 (정적 SQL · SQL_ID 7w2c5n0q8k4xd)
PROCEDURE ITEM_SHOP_SALES(p_itm IN NUMBER, p_tab OUT T_SHOP_SALES_TAB) IS
BEGIN
  SELECT T_SHOP_SALES(h.SHP_NO, SUM(s.SLE_QTY), SUM(s.SLE_QTY * s.SLE_UPRC))
    BULK COLLECT INTO p_tab
    FROM CF11 s, CF10 h
   WHERE s.ITM_NO = p_itm
     AND h.SLE_NO = s.SLE_NO
   GROUP BY h.SHP_NO;
END;

-- 2025-03-04 DBA : 해시 계획(2271840516) 하나만 받아들인 베이스라인으로 올렸다
DECLARE
  n PLS_INTEGER;
BEGIN
  n := DBMS_SPM.LOAD_PLANS_FROM_CURSOR_CACHE(sql_id          => '7w2c5n0q8k4xd',
                                             plan_hash_value => 2271840516);
END;
/

-- 2025-10-06 DBA : Tuning Pack 계약에 맞춰 자동 SQL 튜닝 작업을 다시 켰다
BEGIN
  DBMS_AUTO_TASK_ADMIN.ENABLE(client_name => 'sql tuning advisor', operation => NULL, window_name => NULL);
END;
/

실행계획 학습용 예시 ​

xplan
[DBA_SQL_PLAN_BASELINES · SQL_ID 7w2c5n0q8k4xd]
 PLAN_NAME                       ORIGIN                         ENABLED ACCEPTED CREATED   LAST_VERIFIED   계획
 SQL_PLAN_6s7m9k4c1e2b5a61e4f2   MANUAL-LOAD-FROM-CURSOR-CACHE  YES     YES      25-03-04  -               HASH JOIN (CF11 FULL · CF10 FULL) · 2271840516
 SQL_PLAN_6s7m9k4c1e2b5c93b7d5   AUTO-CAPTURE                   YES     YES      25-03-05  25-10-06 22:14  NESTED LOOPS (IX_CF11_ITM → CF10_PK) · 3658024197

[DBA_ADVISOR_EXECUTIONS · SYS_AUTO_SPM_EVOLVE_TASK]  첫 실행 25-10-06 22:00 · 그 뒤 매일 22:00 · 그 전 기록 없음
[DBA_ADVISOR_PARAMETERS · SYS_AUTO_SPM_EVOLVE_TASK]  ACCEPT_PLANS = TRUE (기본값)

[DBMS_SPM.REPORT_AUTO_EVOLVE_TASK · 10-06 22:00 실행 · 이 SQL 부분만]
 Plan: SQL_PLAN_6s7m9k4c1e2b5c93b7d5
                         Base Plan (HASH)   Test Plan (NL)
   Elapsed Time (s)                .2480            .0060
   Buffer Gets                    13,038              558
   Rows Processed                      5                5
 FINDINGS
  1. The plan was verified in 0.31000 seconds. It passed the benefit criterion because its verified
     performance was 22.91347 times better than that of the baseline plan.
  2. The plan was automatically accepted.
 (시험 실행의 바인드 :B1 = 1096 · 그 계획이 3월 5일 대기 줄에 들어올 때 엿본 값)

[DBA_HIST_SQLSTAT · 7w2c5n0q8k4xd · 날마다]
 날짜    아침 첫 조회의 :B1   그날 계획     EXECUTIONS   BUFFER_GETS   ELAPSED(초)
 10-06   1002                 2271840516          904    11,786,352           224
 10-07   1109                 3658024197          911   281,947,205           742
 10-08   1001                 2271840516          897    11,695,086           221
 10-09   1096                 3658024197          906   280,316,844           737
 10-10   1003                 2271840516          893    11,642,934           220

[10-09 10:21 · :B1 = 1001 · 그날 08:02 첫 조회(1096)로 만든 커서]
Plan hash value: 3658024197

--------------------------------------------------------------------------------------------------------------
| Id  | Operation                             | Name        | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
--------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                      |             |      1 |        |      5 |00:00:02.11 |    834K |
|   1 |  HASH GROUP BY                        |             |      1 |      5 |      5 |00:00:02.11 |    834K |
|   2 |   NESTED LOOPS                        |             |      1 |    140 |    275K|00:00:01.98 |    834K |
|   3 |    NESTED LOOPS                       |             |      1 |    140 |    275K|00:00:01.12 |    559K |
|   4 |     TABLE ACCESS BY INDEX ROWID BATCHED| CF11       |      1 |    140 |    275K|00:00:00.31 |    8697 |
|*  5 |      INDEX RANGE SCAN                 | IX_CF11_ITM |      1 |    140 |    275K|00:00:00.06 |     552 |
|*  6 |     INDEX UNIQUE SCAN                 | CF10_PK     |    275K|      1 |    275K|00:00:00.58 |    550K |
|   7 |    TABLE ACCESS BY INDEX ROWID        | CF10        |    275K|      1 |    275K|00:00:00.71 |    275K |
--------------------------------------------------------------------------------------------------------------

Peeked Binds (identified by position):
--------------------------------------
   1 - :B1 (NUMBER): 1096

Predicate Information (identified by operation id):
---------------------------------------------------
   5 - access("S"."ITM_NO"=:B1)
   6 - access("H"."SLE_NO"="S"."SLE_NO")

[같은 SQL을 두 계획으로 잰 Buffers · 시험 DB(운영 복제)]
 :B1    판매 줄    HASH (2271840516)   NL (3658024197)
 1001   275,135              13,038            834,102
 1002   202,156              13,038            614,873
 1003    30,870              13,038            100,772
 1031     3,032              13,038             11,647
 1109       217              13,038                864
 1096       140              13,038                558

문제 ​

  1. 3월부터 대기 줄에 있던 NL 계획이 10월 6일 밤에 받아들여진 경위를 쓰고, 진화를 통과한 그 계획이 인기 상품에서 지는 이유를 리포트와 바인드 값으로 설명하시오. 이 화면에서 ACS가 이 일을 고쳐 주지 못하는 이유도 쓰시오.
  2. 지금 할 조치와 다시 생기지 않게 할 조치를 SQL로 쓰시오. 그 계획을 지우는 안과 끄는 안의 차이도 쓰시오.
  3. 앞으로 대기 계획을 받아들이는 절차를 쓰고, 두 계획을 모두 살리는 안과 비교해 고른 안의 대가를 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자