Skip to content
튜닝 연습소트 튜닝과 페이징
SORT-067

월별 매출 대시보드가 아침 9시 전에는 3블록인데 첫 주문이 들어온 뒤로는 하루 종일 5,826블록이다

중급DDL한빛유통372 / 700

상황 ​

지난주 DBA가 경영지원팀의 「월별 매출」 대시보드(1분마다 새로 고침, 하루 약 720번)를 빠르게 하려고 구체화 뷰 MV_ORD_MON을 만들고 쿼리 재작성(Query Rewrite)을 켰다. 화면 SQL은 손대지 않았다. 월요일 08:50에 재 보니 3블록이었는데, 오후에 AWR을 보니 이 SQL이 실행마다 5,826블록을 읽고 있었다. 대시보드 요구서에는 "어제까지의 숫자면 된다"라고 적혀 있다. 주문은 9시 무렵부터 낮 동안 계속 들어온다(하루 약 900건). MV는 매일 02:00 잡이 새로 고친다.

테이블과 인덱스 ​

  • T_ORD_BIG: 1,000,000행 · 5,826블록 · ORD_ST COMPLETE 90% · PENDING 8% · CANCEL 2% · 이 문제에서는 인덱스가 없다 · 낮 동안 주문이 계속 INSERT, COMMIT된다
  • MV_ORD_MON 구체화 뷰: 아래 DDL · 108행(36개월 × 상태 3) · 3블록 · 매일 02:00 DBMS_MVIEW.REFRESH('MV_ORD_MON', 'C')
  • 인스턴스 파라미터: QUERY_REWRITE_ENABLED = TRUE · QUERY_REWRITE_INTEGRITY = ENFORCED(기본값)
  • 대시보드는 커넥션 풀의 세션을 정산 화면들과 함께 쓴다. 정산 화면은 오늘 주문까지 정확해야 한다

원본 SQL ​

sql
-- 지난주 DBA 가 만든 MV
CREATE MATERIALIZED VIEW MV_ORD_MON
  BUILD IMMEDIATE
  REFRESH COMPLETE ON DEMAND
  ENABLE QUERY REWRITE
AS
SELECT TRUNC(ORD_DT, 'MM') AS ORD_MM, ORD_ST,
       COUNT(*) AS ORD_CNT, SUM(ORD_AMT) AS ORD_AMT
  FROM T_ORD_BIG
 GROUP BY TRUNC(ORD_DT, 'MM'), ORD_ST;

-- 대시보드 SQL (손대지 않았다)
SELECT TRUNC(ORD_DT, 'MM') AS ORD_MM,
       COUNT(*) AS ORD_CNT, SUM(ORD_AMT) AS ORD_AMT
  FROM T_ORD_BIG
 WHERE ORD_ST <> 'CANCEL'
 GROUP BY TRUNC(ORD_DT, 'MM')
 ORDER BY ORD_MM

실행계획 학습용 예시 ​

xplan
[월요일 08:50 · 새로 고침 뒤 아직 주문이 없을 때]
-------------------------------------------------------------------------------------------------------
| Id  | Operation                     | Name       | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
-------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |            |      1 |        |     36 |00:00:00.01 |       3 |
|   1 |  SORT GROUP BY                |            |      1 |     36 |     36 |00:00:00.01 |       3 |
|*  2 |   MAT_VIEW REWRITE ACCESS FULL| MV_ORD_MON |      1 |     72 |     72 |00:00:00.01 |       3 |
-------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - filter("MV_ORD_MON"."ORD_ST"<>'CANCEL')

[월요일 14:10 · 같은 SQL]
-------------------------------------------------------------------------------------------------------------
| Id  | Operation          | Name      | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Used-Mem |
-------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |           |      1 |        |     36 |00:00:00.31 |    5826 |          |
|   1 |  SORT GROUP BY     |           |      1 |     36 |     36 |00:00:00.31 |    5826 | 4096  (0)|
|*  2 |   TABLE ACCESS FULL| T_ORD_BIG |      1 |    980K|    980K|00:00:00.11 |    5826 |          |
-------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - filter("ORD_ST"<>'CANCEL')

[14:10 · USER_MVIEWS]  MVIEW_NAME MV_ORD_MON · REWRITE_ENABLED Y · STALENESS STALE · LAST_REFRESH_DATE 06/24 02:00:41

문제 ​

  1. 같은 SQL이 08:50에는 MV를 읽고 14:10에는 원장을 읽은 까닭을 MV의 상태와 QUERY_REWRITE_INTEGRITY로 설명하시오.
  2. 요구서(어제까지의 숫자)를 지키면서 하루 종일 MV를 읽도록 고치고, 세션의 QUERY_REWRITE_INTEGRITY를 STALE_TOLERATED로 바꾸는 안과 견주시오.
  3. 고친 뒤에 지켜봐야 할 것(새로 고침 실패, 숫자의 기준 시각)을 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자