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

계획 해시가 그대로라 계획 문제는 아니라던 고객 최근 주문 조회가 실행마다 예순 배 넘게 읽는다

중급SELECT한빛유통644 / 700

상황 ​

고객센터 「최근 완료 주문」 화면은 고객 번호를 받아 완료된 주문을 최신순으로 20건 보여 준다. 상담원 40명이 시간당 1,900번쯤 연다. 목요일 아침부터 화면이 굼뜨다는 말이 나왔다. DBA의 첫 답은 이랬다. "AWR에서 PLAN_HASH_VALUE가 수요일과 같습니다. DISPLAY_AWR로 펼쳐 봐도 계획이 똑같아요. 계획 문제는 아니고 서버 쪽을 봐야겠습니다." 목요일 05:30에는 고객센터 WAS 패치가 있었다(JDBC 드라이버 교체, 연결 설정 파일도 업체 기본값으로 바뀜). DB 쪽 변경은 없었다.

테이블과 인덱스 ​

  • T_ORD_BIG: 1,000,000행 · 5,826블록 · ORD_ST COMPLETE 90% · 고객 2,000명(고객마다 상태가 하나 · COMPLETE 고객 1,800명은 각 500건) · CUS_ID VARCHAR2(40)
  • 인덱스 IX_ORD_BIG_ST_CUS_DT (ORD_ST, CUS_ID, ORD_DT): BLEVEL 2 · 리프 3,330블록(COMPLETE 부분 약 3,000블록)
  • DB 문자셋 AL32UTF8 · 국가별 문자셋 AL16UTF16 · 통계 최신

원본 SQL ​

sql
-- 「최근 완료 주문」 (SQL_ID 0v7c2nq8m4k1x)
SELECT ORD_SEQ, ORD_DT, ORD_AMT
  FROM T_ORD_BIG
 WHERE ORD_ST = 'COMPLETE'
   AND CUS_ID = :cus_id
 ORDER BY ORD_DT DESC
 FETCH FIRST 20 ROWS ONLY

실행계획 학습용 예시 ​

xplan
[DBA_HIST_SQLSTAT · SQL_ID 0v7c2nq8m4k1x]
 SNAP_ID  시작        PLAN_HASH_VALUE  EXECUTIONS_DELTA  BUFFER_GETS_DELTA  실행당 블록
    5210  수 10:00       2786091352             1,820             41,860          23
    5211  수 11:00       2786091352             1,905             43,815          23
    5230  목 10:00       2786091352             1,884          2,865,564       1,521
    5231  목 11:00       2786091352             1,913          2,909,673       1,521

[DBMS_XPLAN.DISPLAY_AWR('0v7c2nq8m4k1x') · 계획 하나 · AWR에는 조건절이 남지 않는다]
 VIEW → WINDOW NOSORT STOPKEY → TABLE ACCESS BY INDEX ROWID T_ORD_BIG → INDEX RANGE SCAN DESCENDING IX_ORD_BIG_ST_CUS_DT

[V$SQL · 지금]  자식 0(LAST_ACTIVE 수 23:58) · 자식 1(목 06:12부터) · 둘 다 PLAN_HASH_VALUE 2786091352
[V$SQL_SHARED_CURSOR · 자식 1]  BIND_MISMATCH = Y
[V$SQL_BIND_CAPTURE]  자식 0  :CUS_ID  VARCHAR2(128)   ·   자식 1  :CUS_ID  NVARCHAR2(128)

[자식 0 · 수요일 · :cus_id = 'CUS_001207']
---------------------------------------------------------------------------------------------------------------
| Id  | Operation                      | Name                 | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
---------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT               |                      |      1 |        |     20 |00:00:00.01 |      23 |
|*  1 |  VIEW                          |                      |      1 |     20 |     20 |00:00:00.01 |      23 |
|*  2 |   WINDOW NOSORT STOPKEY        |                      |      1 |    500 |     20 |00:00:00.01 |      23 |
|   3 |    TABLE ACCESS BY INDEX ROWID | T_ORD_BIG            |      1 |    500 |     20 |00:00:00.01 |      23 |
|*  4 |     INDEX RANGE SCAN DESCENDING| IX_ORD_BIG_ST_CUS_DT |      1 |    500 |     20 |00:00:00.01 |       3 |
---------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter("from$_subquery$_002"."rowlimit_$$_rownumber"<=20)
   2 - filter(ROW_NUMBER() OVER ( ORDER BY INTERNAL_FUNCTION("ORD_DT") DESC )<=20)
   4 - access("ORD_ST"='COMPLETE' AND "CUS_ID"=:CUS_ID)

[자식 1 · 목요일 · 같은 고객]
---------------------------------------------------------------------------------------------------------------
| Id  | Operation                      | Name                 | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
---------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT               |                      |      1 |        |     20 |00:00:00.03 |    1212 |
|*  1 |  VIEW                          |                      |      1 |     20 |     20 |00:00:00.03 |    1212 |
|*  2 |   WINDOW NOSORT STOPKEY        |                      |      1 |   9000 |     20 |00:00:00.03 |    1212 |
|   3 |    TABLE ACCESS BY INDEX ROWID | T_ORD_BIG            |      1 |   9000 |     20 |00:00:00.03 |    1212 |
|*  4 |     INDEX RANGE SCAN DESCENDING| IX_ORD_BIG_ST_CUS_DT |      1 |   9000 |     20 |00:00:00.03 |    1192 |
---------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter("from$_subquery$_002"."rowlimit_$$_rownumber"<=20)
   2 - filter(ROW_NUMBER() OVER ( ORDER BY INTERNAL_FUNCTION("ORD_DT") DESC )<=20)
   4 - access("ORD_ST"='COMPLETE')
       filter(SYS_OP_C2C("CUS_ID")=:CUS_ID)

문제 ​

  1. 계획 해시가 같은데 실행당 블록이 크게 늘어난 이유를 두 자식 커서의 Predicate Information으로 설명하시오. DBA가 AWR만 보고 계획이 같다고 판단한 것의 한계도 쓰시오.
  2. 무엇이 조건절을 바꿨는지 찾아 쓰고, 화면이 다시 23블록 안팎으로 읽도록 고치시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자