Skip to content
튜닝 연습파싱과 바인드 변수
PARSE-021

오후 2시만 넘으면 보류 판매 목록 조회가 Top SQL에 올라왔다가 다음 날 아침이면 사라진다

기초PLSQL유통566 / 700

상황 ​

본사 레거시 유통의 매장 단말 6대는 「정산 보류 판매」 목록을 1분마다 새로 고친다(08:00 ~ 22:00, 하루 약 5,000번). 단말은 프로시저 PKG_SLE_HOLD.hold_list를 부르고, 넘기는 판매 구분은 늘 '90'(정산 보류)이다. 지난주 반품 정산 배치를 새로 연 뒤로 DBA가 이상한 모양을 보고했다. "오후 2시부터 이 조회가 Top SQL 1위로 올라오고, 다음 날 아침에는 아무 일 없었다는 듯 사라집니다. 배치가 2시에 같은 SQL을 한 번 부르기는 하지만 한 번뿐이고, 보류 목록 SQL은 아무도 손대지 않았어요."

테이블과 인덱스 ​

  • CF10 판매: 500,000행 · 4,583블록 · SLE_NO VARCHAR2(15) · SLE_DT VARCHAR2(8) · SLE_HMS · SHP_NO · SLE_NO 순(날짜순)으로 적재
  • 이 문제의 변형 컬럼 SLE_DV_CD VARCHAR2(2) 판매 구분: '10' 정상 488,600건 · '20' 반품 11,300건 · '90' 정산 보류 100건 · FREQUENCY 히스토그램 · 반품과 보류는 날짜와 상관없이 테이블 곳곳에 흩어져 있다
  • 인덱스 IX_CF10_DV_CD (SLE_DV_CD): 높이 3 · 리프 약 1,000블록
  • 통계는 매일 01:00에 모은다(no_invalidate => FALSE). 그래서 이 문장의 커서는 매일 밤 무효화되고, 그 뒤 첫 호출은 08:00 단말이다.
  • DB 문자셋 AL32UTF8 · NLS_LENGTH_SEMANTICS = BYTE · CURSOR_SHARING = EXACT · 바인드 피킹 켬

원본 SQL ​

sql
-- PKG_SLE_HOLD.hold_list (2018) : 매장 단말이 1분마다 부른다 (단말은 늘 '90'을 넘긴다)
PROCEDURE hold_list(p_cd IN VARCHAR2, rc OUT SYS_REFCURSOR) IS
  v_cd  VARCHAR2(2) := p_cd;
BEGIN
  OPEN rc FOR
    SELECT SLE_NO, SLE_DT, SLE_HMS, SHP_NO
      FROM CF10
     WHERE SLE_DV_CD = v_cd;
END;

-- PKG_RTN_STLM.run (지난주 신규) : 반품 정산 · 매일 14:00에 한 번 · SQL은 위 패키지에서 복사했다
PROCEDURE run IS
  v_cd  VARCHAR2(100) := '20';        -- 구분 코드가 길어질지 몰라 넉넉하게
BEGIN
  FOR r IN (SELECT SLE_NO, SLE_DT, SLE_HMS, SHP_NO
              FROM CF10
             WHERE SLE_DV_CD = v_cd) LOOP
    ins_rtn_stlm(r.SLE_NO, r.SLE_DT, r.SHP_NO);
  END LOOP;
  COMMIT;
END;

실행계획 학습용 예시 · 바인드 그릇 크기는 교재 11화 실측과 같은 단위 ​

xplan
[V$SQL · 16:00 · 텍스트 하나에 자식 둘]
 SQL_TEXT : SELECT SLE_NO, SLE_DT, SLE_HMS, SHP_NO FROM CF10 WHERE SLE_DV_CD = :B1
 CHILD  IS_SHAREABLE  :B1 MAX_LENGTH  PLAN_HASH   EXECUTIONS  BUFFER_GETS/EXEC  FIRST_LOAD_TIME      처음 부른 곳
     0  N                         32  2704158391       2,160               103  2023-06-15/08:00:04  PKG_SLE_HOLD.hold_list
     1  Y                        128  1186043257         721             4,587  2023-06-15/14:00:02  PKG_RTN_STLM.run

[V$SQL_SHARED_CURSOR · Y인 칸]   child 1 : BIND_LENGTH_UPGRADEABLE = Y

[자식 0 · 단말 한 번 (08:00 ~ 13:59) · FORMAT 'ALLSTATS LAST +PEEKED_BINDS']
Plan hash value: 2704158391

-------------------------------------------------------------------------------------------------------------
| Id  | Operation                            | Name          | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
-------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                     |               |      1 |        |    100 |00:00:00.01 |     103 |
|   1 |  TABLE ACCESS BY INDEX ROWID BATCHED | CF10          |      1 |    100 |    100 |00:00:00.01 |     103 |
|*  2 |   INDEX RANGE SCAN                   | IX_CF10_DV_CD |      1 |    100 |    100 |00:00:00.01 |       3 |
-------------------------------------------------------------------------------------------------------------

Peeked Binds (identified by name):
----------------------------------
   1 - :B1 (VARCHAR2(32), CSID=873): '90'

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - access("SLE_DV_CD"=:B1)

[자식 1 · 단말 한 번 (14:00 이후) · FORMAT 'ALLSTATS LAST +PEEKED_BINDS']
Plan hash value: 1186043257

-----------------------------------------------------------------------------------
| Id  | Operation         | Name | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
-----------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |      1 |        |    100 |00:00:00.04 |    4587 |
|*  1 |  TABLE ACCESS FULL| CF10 |      1 |  11300 |    100 |00:00:00.04 |    4587 |
-----------------------------------------------------------------------------------

Peeked Binds (identified by name):
----------------------------------
   1 - :B1 (VARCHAR2(128), CSID=873): '20'

Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter("SLE_DV_CD"=:B1)

[그제와 어제도 같다]  08:00 자식 0('90', 인덱스) → 14:00 자식 1('20', FULL) → 01:00 통계 수집으로 무효화

문제 ​

  1. 오후 2시부터 보류 목록이 테이블 전체를 읽는 이유를 위 자료로 설명하시오. 다음 날 아침에는 다시 인덱스로 돌아오는 이유도 쓰시오.
  2. 시각과 상관없이 보류 목록이 인덱스를 타도록 두 프로시저를 고치시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자