Skip to content
튜닝 연습서브쿼리와 쿼리 변환
SUBQ-056

2022년 주문을 아카이브로 떼어 낸 다음 날, 기간 매출은 22배, 고객 최근 주문은 175배 무거워졌다

실전SELECT한빛유통291 / 700

상황 ​

지난 주말 한빛유통은 T_ORD_BIG의 2022년 주문을 아카이브 테이블로 떼어 냈다. 화면 SQL을 고치지 않으려고 원래 테이블은 CTAS로 두 개(T_ORD_BIG_CUR, T_ORD_BIG_ARC)로 나눠 만든 뒤 지우고, 원래 이름으로 UNION ALL 뷰를 만들었다. 아카이브는 "조회가 거의 없을 테니" 인덱스를 만들지 않았다. 월요일에 잰 결과는 다음과 같다. (A) 정산 화면 「기간 매출」(하루 3,000번) 91 → 2,033블록, (B) 콜센터 「고객 최근 주문 10건」(하루 30,000번) 13 → 2,278블록, (C) 월 리포트 「3년 등급별 매출」(하루 20번) 5,856 → 5,856블록. 박팀장은 뷰를 유지할지, 파티션으로 갈지까지 포함해 안을 내라고 했다.

테이블과 인덱스 ​

  • T_ORD_BIG_CUR(2023 ~ 2024년): 666,000행 · 3,884블록 · IX_CUR_DT_AMT (ORD_DT, ORD_AMT) · IX_CUR_CUS_DT (CUS_ID, ORD_DT) (원래 테이블의 인덱스를 다시 만들었다)
  • T_ORD_BIG_ARC(2022년): 334,000행 · 1,942블록 · 인덱스 없음 · 앞으로 DML이 없다
  • 고객 2,000명이 약 500건씩(현재 약 333건, 아카이브 약 167건) · 한 고객의 주문은 테이블 곳곳에 흩어져 있다 · 2024년 6월 27,390건
  • M_CUS: 2,000행 · 30블록 · 두 테이블 모두 통계 최신 · Partitioning 옵션은 라이선스가 있다

원본 SQL ​

sql
-- 원래 이름의 뷰
CREATE OR REPLACE VIEW T_ORD_BIG AS
SELECT ORD_SEQ, CUS_ID, ORD_DT, ORD_ST, ORD_AMT FROM T_ORD_BIG_CUR
UNION ALL
SELECT ORD_SEQ, CUS_ID, ORD_DT, ORD_ST, ORD_AMT FROM T_ORD_BIG_ARC;

-- (A) 기간 매출 (하루 3,000번)
SELECT COUNT(*), SUM(ORD_AMT)
  FROM T_ORD_BIG
 WHERE ORD_DT >= :d1
   AND ORD_DT <  :d2;

-- (B) 고객 최근 주문 10건 (하루 30,000번)
SELECT *
  FROM (SELECT ORD_SEQ, ORD_DT, ORD_ST, ORD_AMT
          FROM T_ORD_BIG
         WHERE CUS_ID = :cus
         ORDER BY ORD_DT DESC)
 WHERE ROWNUM <= 10;

-- (C) 3년 등급별 매출 (하루 20번) : M_CUS와 해시 조인, 두 테이블 전체 스캔 · 5,856블록 · 고칠 것 없음

실행계획 학습용 예시 ​

xplan
[A · 2024년 6월]
----------------------------------------------------------------------------------------------
| Id  | Operation             | Name          | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
----------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |               |      1 |        |      1 |00:00:00.03 |    2033 |
|   1 |  SORT AGGREGATE       |               |      1 |      1 |      1 |00:00:00.03 |    2033 |
|   2 |   VIEW                | T_ORD_BIG     |      1 |  27391 |  27390 |00:00:00.03 |    2033 |
|   3 |    UNION-ALL          |               |      1 |        |  27390 |00:00:00.03 |    2033 |
|*  4 |     INDEX RANGE SCAN  | IX_CUR_DT_AMT |      1 |  27390 |  27390 |00:00:00.01 |      91 |
|*  5 |     TABLE ACCESS FULL | T_ORD_BIG_ARC |      1 |      1 |      0 |00:00:00.02 |    1942 |
----------------------------------------------------------------------------------------------
   4 - access("ORD_DT">=:D1 AND "ORD_DT"<:D2)
   5 - filter(("ORD_DT">=:D1 AND "ORD_DT"<:D2))

[B]
-----------------------------------------------------------------------------------------------------------
| Id  | Operation                             | Name          | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
-----------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                      |               |      1 |        |     10 |00:00:00.04 |    2278 |
|*  1 |  COUNT STOPKEY                        |               |      1 |        |     10 |00:00:00.04 |    2278 |
|   2 |   VIEW                                |               |      1 |    500 |     10 |00:00:00.04 |    2278 |
|*  3 |    SORT ORDER BY STOPKEY              |               |      1 |    500 |     10 |00:00:00.04 |    2278 |
|   4 |     VIEW                              | T_ORD_BIG     |      1 |    500 |    500 |00:00:00.04 |    2278 |
|   5 |      UNION-ALL                        |               |      1 |        |    500 |00:00:00.04 |    2278 |
|   6 |       TABLE ACCESS BY INDEX ROWID BATCHED| T_ORD_BIG_CUR |   1 |    333 |    333 |00:00:00.01 |     336 |
|*  7 |        INDEX RANGE SCAN               | IX_CUR_CUS_DT |      1 |    333 |    333 |00:00:00.01 |       3 |
|*  8 |       TABLE ACCESS FULL               | T_ORD_BIG_ARC |      1 |    167 |    167 |00:00:00.03 |    1942 |
-----------------------------------------------------------------------------------------------------------
   1 - filter(ROWNUM<=10)
   3 - filter(ROWNUM<=10)
   7 - access("CUS_ID"=:CUS)
   8 - filter("CUS_ID"=:CUS)

문제 ​

  1. (A)와 (B)에서 바깥 조건이 UNION ALL의 두 갈래에 어떻게 들어갔고, 무엇 때문에 일량이 늘었는지 줄 번호로 설명하시오. (B)에서 ROWNUM <= 10이 어디에 걸렸는지도 쓰시오.
  2. 뷰 정의와 인덱스, (B)의 SQL을 고쳐 (A)와 (B)를 아카이브 이전 수준으로 되돌리고, 하루 읽기량이 얼마나 바뀌는지 쓰시오.
  3. UNION ALL 뷰 대신 T_ORD_BIG을 연도별 RANGE 파티션 테이블로 바꾸는 안과 비교해 장단점을 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자