Skip to content
튜닝 연습인덱스 스캔 효율과 테이블 액세스
ACC-054

주문서 화면을 빠르게 하려고 주문과 주문상세를 같은 블록에 묶자는 제안서가 올라왔다

실전DDL한빛유통129 / 700

상황 ​

쇼핑몰 「주문서 상세」 화면은 주문 한 건과 그 주문의 상세 1 ~ 3줄을 함께 보여 준다. 고객과 상담원이 하루 약 5만 번 연다. 조민우의 제안서는 이렇다. "주문과 주문상세는 늘 ORD_SEQ로 같이 읽는다. 두 테이블을 ORD_SEQ 인덱스 클러스터에 같이 넣으면 한 블록에서 끝난다." 복사본으로 재 보니 주문서 상세는 6블록에서 4블록이 됐다. 박팀장은 "주문 테이블을 읽는 다른 SQL까지 하루 합계로 따져 오라"고 했다.

테이블과 인덱스 ​

  • T_ORD 주문: 100,000행 · 896블록(블록당 약 112행) · ORD_SEQ 순으로 쌓였다 · PK_T_ORD (ORD_SEQ) BLEVEL 1 · 다른 인덱스는 없다 · 2024년 6월 4,110건
  • T_ORD_DET 주문상세: 200,000행 · 896블록(블록당 약 223행) · 주문당 1 ~ 3줄 · (ORD_SEQ, ORD_DET_NO) 순으로 쌓였다 · PK_T_ORD_DET (ORD_SEQ, ORD_DET_NO) BLEVEL 1 · CLUSTERING_FACTOR 약 900
  • 복사본: 인덱스 클러스터 C_ORD (ORD_SEQ) · SIZE 160 · 클러스터 인덱스 C_ORD_IDX BLEVEL 1 · 두 테이블을 담은 클러스터 블록 약 2,010개
  • 하루 실행 횟수: S1 주문서 상세 약 5만 번 · S2 「월간 결제수단별 매출」(정산 대시보드) 약 300번 · S3 야간 정산 적재(T_ORD를 한 번 읽어 T_ORD_STLM에 APPEND) 1번 · 주문 입력 하루 약 140건

원본 SQL ​

sql
-- 제안서의 DDL
CREATE CLUSTER C_ORD (ORD_SEQ NUMBER(18)) SIZE 160;
CREATE INDEX C_ORD_IDX ON CLUSTER C_ORD;
CREATE TABLE T_ORD_C     CLUSTER C_ORD (ORD_SEQ) AS SELECT * FROM T_ORD;
CREATE TABLE T_ORD_DET_C CLUSTER C_ORD (ORD_SEQ) AS SELECT * FROM T_ORD_DET;

-- S1 주문서 상세
SELECT o.ORD_SEQ, o.ORD_DT, o.ORD_ST, o.PAY_TP, o.ORD_AMT,
       d.ORD_DET_NO, d.ITM_ID, d.ORD_QTY, d.UNT_PRC
  FROM T_ORD o, T_ORD_DET d
 WHERE o.ORD_SEQ = :v_ord
   AND d.ORD_SEQ = o.ORD_SEQ;

-- S2 월간 결제수단별 매출
SELECT PAY_TP, COUNT(*), SUM(PAY_AMT)
  FROM T_ORD
 WHERE ORD_DT >= TO_DATE(:v_ym, 'YYYYMM')
   AND ORD_DT <  ADD_MONTHS(TO_DATE(:v_ym, 'YYYYMM'), 1)
 GROUP BY PAY_TP;

실행계획 학습용 예시 · 행 수와 쌓인 순서는 적재 스크립트 그대로 · 클러스터 블록 수는 SIZE로 센 어림값 ​

xplan
[운영 · S1]
-----------------------------------------------------------------------------------------------------------------
| Id  | Operation                             | Name         | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
-----------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                      |              |      1 |        |      2 |00:00:00.01 |       6 |
|   1 |  NESTED LOOPS                         |              |      1 |      2 |      2 |00:00:00.01 |       6 |
|   2 |   TABLE ACCESS BY INDEX ROWID         | T_ORD        |      1 |      1 |      1 |00:00:00.01 |       3 |
|*  3 |    INDEX UNIQUE SCAN                  | PK_T_ORD     |      1 |      1 |      1 |00:00:00.01 |       2 |
|   4 |   TABLE ACCESS BY INDEX ROWID BATCHED | T_ORD_DET    |      1 |      2 |      2 |00:00:00.01 |       3 |
|*  5 |    INDEX RANGE SCAN                   | PK_T_ORD_DET |      1 |      2 |      2 |00:00:00.01 |       2 |
-----------------------------------------------------------------------------------------------------------------
   3 - access("O"."ORD_SEQ"=:V_ORD)
   5 - access("D"."ORD_SEQ"=:V_ORD)

[복사본 · S1]
-----------------------------------------------------------------------------------------------------------------
| Id  | Operation                             | Name         | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
-----------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                      |              |      1 |        |      2 |00:00:00.01 |       4 |
|   1 |  NESTED LOOPS                         |              |      1 |      2 |      2 |00:00:00.01 |       4 |
|   2 |   TABLE ACCESS CLUSTER                | T_ORD_C      |      1 |      1 |      1 |00:00:00.01 |       3 |
|*  3 |    INDEX UNIQUE SCAN                  | C_ORD_IDX    |      1 |      1 |      1 |00:00:00.01 |       2 |
|*  4 |   TABLE ACCESS CLUSTER                | T_ORD_DET_C  |      1 |      2 |      2 |00:00:00.01 |       1 |
-----------------------------------------------------------------------------------------------------------------
   3 - access("O"."ORD_SEQ"=:V_ORD)
   4 - filter("D"."ORD_SEQ"=:V_ORD)

[운영 · S2 · 2024년 6월]
-----------------------------------------------------------------------------------------------------------------
| Id  | Operation                             | Name         | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
-----------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                      |              |      1 |        |      3 |00:00:00.01 |     896 |
|   1 |  HASH GROUP BY                        |              |      1 |      3 |      3 |00:00:00.01 |     896 |
|*  2 |   TABLE ACCESS FULL                   | T_ORD        |      1 |   4110 |   4110 |00:00:00.01 |     896 |
-----------------------------------------------------------------------------------------------------------------

[복사본 · S2 · 2024년 6월]
-----------------------------------------------------------------------------------------------------------------
| Id  | Operation                             | Name         | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
-----------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                      |              |      1 |        |      3 |00:00:00.02 |    2010 |
|   1 |  HASH GROUP BY                        |              |      1 |      3 |      3 |00:00:00.02 |    2010 |
|*  2 |   TABLE ACCESS FULL                   | T_ORD_C      |      1 |   4110 |   4110 |00:00:00.02 |    2010 |
-----------------------------------------------------------------------------------------------------------------
   2 - filter(("ORD_DT">=TO_DATE(:V_YM,'YYYYMM') AND "ORD_DT"<ADD_MONTHS(TO_DATE(:V_YM,'YYYYMM'),1)))

문제 ​

  1. 클러스터로 S1이 줄어든 것이 2블록뿐인 이유를 주문상세가 지금 쌓여 있는 모양으로 설명하시오. S2가 896블록에서 2,010블록이 되는 이유도 설명하시오.
  2. S1, S2, S3의 실행 횟수로 하루 읽는 양을 비교하고 제안을 받아들일지 판단하시오. 판단의 근거를 확인하는 SQL과 필요한 DDL을 작성하시오.
  3. 클러스터 테이블의 제약이 주문 테이블에 주는 영향을 설명하고, 인덱스 클러스터가 맞는 테이블의 조건을 정리하시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자