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

본부장이 매장 150곳을 고르면 같은 화면이 날마다 0.2초였다 3초였다 한다

실전SELECT버거598 / 700

상황 ​

버거 본사의 「매장 묶음 시간대 분석」 화면은 사용자가 매장을 고르면(1곳 ~ 200곳) 매장 번호를 전역 임시 테이블 GT_SHOP_PICK에 넣고 아래 SQL을 실행한다. 연도는 화면이 리터럴로 넣고, 바인드 변수는 없다. 사용자는 점장, 지역장, 본부장 약 30명이고 하루 약 400번 쓴다. 박팀장이 전했다. "본부장님이 150곳을 고르면 어떤 날은 바로 뜨고 어떤 날은 3초가 넘는답니다." 조민우가 공유 풀을 보니 느린 날의 커서는 그날 08:40에 한 점장이 매장 한 곳을 골라 하드 파싱한 것이었다.

테이블과 인덱스 ​

  • GT_SHOP_PICK: 전역 임시 테이블(ON COMMIT PRESERVE ROWS) · SHOP_NO VARCHAR2(6) · 세션마다 1 ~ 200행 · 통계 없음(아무도 모으지 않았다) · GLOBAL_TEMP_TABLE_STATS는 기본값 SESSION
  • ORD: 1,000,000행 · 22,422블록 · 2013년 500,218행 · 매장당 한 해 약 2,500행 · 날짜순 적재
  • 인덱스: ORD_PK (ORD_NO) · ORD_X01 (ORD_DT, ORD_HMS) · ORD_X02 (SHOP_NO, ORD_DT)(클러스터링 팩터 약 99만, 한 매장의 주문은 테이블 곳곳에 흩어져 있다)
  • optimizer_dynamic_sampling 2(기본값)

원본 SQL ​

sql
-- 화면 : INSERT INTO GT_SHOP_PICK (SHOP_NO) VALUES (...) 를 고른 매장 수만큼 · 그다음
SELECT o.SHOP_NO, SUBSTR(o.ORD_HMS, 1, 2) AS hh, COUNT(*) AS ord_cnt
  FROM GT_SHOP_PICK g, ORD o
 WHERE o.SHOP_NO = g.SHOP_NO
   AND o.ORD_DT BETWEEN '20130101' AND '20131231'
 GROUP BY o.SHOP_NO, SUBSTR(o.ORD_HMS, 1, 2)

실행계획 학습용 예시 ​

xplan
[느린 날 10:05 · 본부장 150곳 · 커서는 08:40 점장(1곳)이 하드 파싱한 것]
---------------------------------------------------------------------------------------------------
| Id  | Operation                     | Name         | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
---------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |              |      1 |        |   2400 |00:00:03.12 |     378K|
|   1 |  HASH GROUP BY                |              |      1 |   2501 |   2400 |00:00:03.12 |     378K|
|   2 |   NESTED LOOPS                |              |      1 |   2501 |    375K|00:00:02.96 |     378K|
|   3 |    NESTED LOOPS               |              |      1 |   2501 |    375K|00:00:00.61 |    1653 |
|   4 |     TABLE ACCESS FULL         | GT_SHOP_PICK |      1 |      1 |    150 |00:00:00.01 |       3 |
|*  5 |     INDEX RANGE SCAN          | ORD_X02      |    150 |   2501 |    375K|00:00:00.42 |    1650 |
|   6 |    TABLE ACCESS BY INDEX ROWID| ORD          |    375K|      1 |    375K|00:00:02.18 |     376K|
---------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   5 - access("O"."SHOP_NO"="G"."SHOP_NO" AND "O"."ORD_DT">='20130101' AND "O"."ORD_DT"<='20131231')

Note
-----
   - dynamic statistics used: dynamic sampling (level=2)

[빠른 날 · 그날 첫 사용자가 지역장(120곳)이었다]
---------------------------------------------------------------------------------------
| Id  | Operation           | Name         | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
---------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT    |              |      1 |        |   2400 |00:00:00.21 |   22425 |
|   1 |  HASH GROUP BY      |              |      1 |   2856 |   2400 |00:00:00.21 |   22425 |
|*  2 |   HASH JOIN         |              |      1 |    300K|    375K|00:00:00.17 |   22425 |
|   3 |    TABLE ACCESS FULL| GT_SHOP_PICK |      1 |    120 |    150 |00:00:00.01 |       3 |
|*  4 |    TABLE ACCESS FULL| ORD          |      1 |    500K|    500K|00:00:00.09 |   22422 |
---------------------------------------------------------------------------------------

[같은 두 계획을 점장(1곳)이 쓰면]  NL 계획 Buffers 2,515 · 0.02초 / 해시 계획 Buffers 22,425 · 0.15초

문제 ​

  1. 같은 SQL이 날마다 다른 계획으로 도는 이유를 E-Rows와 Note, 커서 공유로 설명하시오. 바인드 변수가 없는데도 그날 첫 사용자가 계획을 정하는 까닭과, 적응적 커서 공유(ACS)가 도울 수 없는 까닭도 쓰시오.
  2. 네 가지 안(동적 샘플링 힌트, 세션 전용 통계, 고른 매장 수로 SQL 나누기, 계획 하나로 고정)을 읽는 양과 부작용으로 비교하시오.
  3. 고른 안을 SQL로 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자