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

본사 일 매출 화면이 어떤 날은 0.01초, 어떤 날은 0.3초 걸린다

중급SELECT버거565 / 700

상황 ​

본사 「매장별 일 주문 현황」 화면(주문 건수, 첫 주문과 마지막 주문 시각)은 점장과 본사 직원이 전날 하루를 넘겨 연다(하루 약 2,000번). 같은 DAO 메서드를 월 정산 리포트도 쓰는데, 리포트는 한 달을 넘긴다. 리포트는 낮에 본사 직원이 열기도 하고 밤에 예약 실행되기도 한다(합쳐서 하루 30번쯤). 분석용 사본은 매일 01:00에 새로 적재하고 바로 통계를 기본값으로 모은다. 오과장이 말했다. "어떤 날은 괜찮고 어떤 날은 느린데, 그날그날 달라요. 배포도 없었고 SQL도 그대로예요."

테이블과 인덱스 ​

  • ORD_RANDOM: 1,000,000행 · 22,414블록 · POS 원장 ORD를 무작위 순서로 복제한 본사 분석용 사본 · ORD_DT VARCHAR2(8) · 하루 약 1,370건 · 매장 200곳
  • 인덱스 ORD_RANDOM_X01 (ORD_DT, ORD_HMS): CF 999,958 · BLEVEL 2 · 리프 3,774블록 · 이 문제에서는 다른 인덱스가 없다(화면은 SHOP_NO와 ORD_HMS를 함께 읽으므로 테이블까지 간다)
  • 통계: 매일 01:00 GATHER_TABLE_STATS 기본값(no_invalidate => DBMS_STATS.AUTO_INVALIDATE) · 히스토그램 없음
  • CURSOR_SHARING = EXACT · 바인드 피킹 켬 · statistics_level = TYPICAL

원본 SQL ​

sql
-- SalesDao.shopOrderCount(from, to) : 화면은 from = to = 전날, 월 정산 리포트는 한 달
SELECT SHOP_NO, COUNT(*) AS ORD_CNT, MIN(ORD_HMS) AS FIRST_HMS, MAX(ORD_HMS) AS LAST_HMS
  FROM ORD_RANDOM
 WHERE ORD_DT BETWEEN :f AND :t
 GROUP BY SHOP_NO

실행계획 학습용 예시 ​

xplan
[DBA_HIST_SQLSTAT — 같은 SQL_ID · 날마다]
 요일  PLAN_HASH    실행    BUFFER_GETS/실행   커서가 다시 만들어진 시각 · 그때 온 호출
 월    1648203117   2,031          1,975       04:12 · 화면(하루, 첫 점장)
 화    3920571642   2,036         22,415       02:37 · 월 정산 리포트(한 달, 야간 예약 실행)
 수    1648203117   2,029          1,974       05:48 · 화면(하루)
 목    3920571642   2,033         22,416       03:05 · 월 정산 리포트(한 달)

[V$SQL_SHARED_CURSOR — 목요일 새 자식] ROLL_INVALID_MISMATCH = Y
[V$SQL — 목요일 자식] IS_BIND_SENSITIVE Y · IS_BIND_AWARE N · V$SQL_CS_HISTOGRAM 0:0 1:2,033 2:0

[목요일 자식 · 화면(하루) 한 번 · FORMAT 'ALLSTATS LAST +PEEKED_BINDS']
Plan hash value: 3920571642

------------------------------------------------------------------------------------------------------------
| Id  | Operation                             | Name           | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Used-Mem |
------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                      |                |      1 |        |    200 |00:00:00.29 |   22414 |          |
|   1 |  HASH GROUP BY                        |                |      1 |    200 |    200 |00:00:00.29 |   22414 | 1394K (0)|
|*  2 |   FILTER                              |                |      1 |        |   1370 |00:00:00.28 |   22414 |          |
|*  3 |    TABLE ACCESS FULL                  | ORD_RANDOM     |      1 |  41100 |   1370 |00:00:00.28 |   22414 |          |
------------------------------------------------------------------------------------------------------------

Peeked Binds (identified by name):
----------------------------------
   1 - :F (VARCHAR2(30), CSID=873): '20131101'
   2 - :T (VARCHAR2(30), CSID=873): '20131130'

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - filter(:F<=:T)
   3 - filter(("ORD_DT">=:F AND "ORD_DT"<=:T))

[수요일 자식 · 리포트(한 달) 한 번 · 이날 커서는 화면의 하루로 태어났다]
------------------------------------------------------------------------------------------------------------
| Id  | Operation                             | Name           | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Used-Mem |
------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                      |                |      1 |        |    200 |00:00:00.61 |   41825 |          |
|   1 |  HASH GROUP BY                        |                |      1 |    200 |    200 |00:00:00.61 |   41825 | 1394K (0)|
|*  2 |   FILTER                              |                |      1 |        |  41666 |00:00:00.58 |   41825 |          |
|   3 |    TABLE ACCESS BY INDEX ROWID BATCHED| ORD_RANDOM     |      1 |   1370 |  41666 |00:00:00.57 |   41825 |          |
|*  4 |     INDEX RANGE SCAN                  | ORD_RANDOM_X01 |      1 |   1370 |  41666 |00:00:00.02 |     160 |          |
------------------------------------------------------------------------------------------------------------
   2 - filter(:F<=:T)
   4 - access("ORD_DT">=:F AND "ORD_DT"<=:T)

[같은 날 화면(하루) 한 번 · 인덱스 계획]  Buffers 1,377 · A-Time 0.01초

문제 ​

  1. 배포도 없었는데 날마다 계획이 바뀌는 이유를 통계 수집 방식과 바인드 피킹으로 설명하시오. 커서가 다시 만들어지는 시각이 날마다 다른 이유도 쓰시오.
  2. 적응적 커서 공유(ACS)가 이 문장을 고쳐 주지 못하는 이유를 위 자료로 쓰시오.
  3. 어느 날이든 화면과 리포트가 각자 맞는 계획을 타도록 SQL을 고치시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자