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

상담 많은 고객 200명의 분기 주문 실적을 보는데 주문 인덱스를 통째로 읽는다

기초SELECT한빛유통616 / 700

상황 ​

고객관리팀의 「전담 상담 고객 실적」 리포트는 A와 B 등급 고객 200명의 상담 건수와 분기 주문 실적을 고객마다 한 줄씩 보여 준다. 팀장 회의 전에 하루 수십 번 돌린다. 분기는 화면이 리터럴로 넣는다(아래는 2024년 2분기). 정하은이 계획을 보고 물었다. "고객 200명의 주문만 보면 되는데 왜 주문 인덱스를 통째로 읽죠?" 이 리포트 SQL은 다른 팀도 같이 쓰고 있어서 이번에는 고치지 않기로 했다.

테이블과 인덱스 ​

  • T_CS_CALL 전담 상담 이력(이 문제용): 36,000행 · 260블록 · CALL_SEQ · CALL_DT · CUS_ID · AGT_ID(전담 상담원) · RGN_ID(고객 지역) · A와 B 등급 고객 200명의 3년치 상담만 쌓인다
  • 고객마다 전담 상담원은 한 명이고 지역도 하나다(상담원 12명 · 지역 8곳). 그래서 (CUS_ID, AGT_ID, RGN_ID) 조합은 고객 수와 같은 200개다.
  • T_CS_CALL 통계: 최신 · NUM_DISTINCT는 CUS_ID 200 · AGT_ID 12 · RGN_ID 8 · 히스토그램 없음 · 확장 통계 없음
  • T_ORD_BIG: 1,000,000행 · 5,826블록 · 고객 2,000명 × 500건 · ORD_DT NDV 1,095(2022-01-01 ~ 2024-12-30) · 2024년 4 ~ 6월 주문 83,083건(고객 200명의 몫은 8,308건)
  • 인덱스 IX_ORD_BIG_CUS_DT_AMT (CUS_ID, ORD_DT, ORD_AMT): BLEVEL 2 · 리프 3,950 · 이 SQL이 T_ORD_BIG에서 쓰는 열을 모두 담는다

원본 SQL ​

sql
SELECT v.CUS_ID, v.AGT_ID, v.RGN_ID, v.call_cnt,
       COUNT(*) AS ord_cnt, SUM(o.ORD_AMT) AS ord_amt
  FROM (SELECT CUS_ID, AGT_ID, RGN_ID, COUNT(*) AS call_cnt
          FROM T_CS_CALL
         GROUP BY CUS_ID, AGT_ID, RGN_ID) v,
       T_ORD_BIG o
 WHERE o.CUS_ID = v.CUS_ID
   AND o.ORD_DT >= DATE '2024-04-01'
   AND o.ORD_DT <  DATE '2024-07-01'
 GROUP BY v.CUS_ID, v.AGT_ID, v.RGN_ID, v.call_cnt

실행계획 학습용 예시 ​

xplan
Plan hash value: 3318260471

-------------------------------------------------------------------------------------------------------------------
| Id  | Operation               | Name                  | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Used-Mem |
-------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT        |                       |      1 |        |    200 |00:00:00.09 |    4231 |          |
|   1 |  HASH GROUP BY          |                       |      1 |  19200 |    200 |00:00:00.09 |    4231 | 1394K (0)|
|*  2 |   HASH JOIN             |                       |      1 |    807K|   8308 |00:00:00.08 |    4231 | 2210K (0)|
|   3 |    VIEW                 |                       |      1 |  19200 |    200 |00:00:00.01 |     260 |          |
|   4 |     HASH GROUP BY       |                       |      1 |  19200 |    200 |00:00:00.01 |     260 | 1188K (0)|
|   5 |      TABLE ACCESS FULL  | T_CS_CALL             |      1 |  36000 |  36000 |00:00:00.01 |     260 |          |
|*  6 |    INDEX FAST FULL SCAN | IX_ORD_BIG_CUS_DT_AMT |      1 |  84094 |  83083 |00:00:00.05 |    3971 |          |
-------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - access("O"."CUS_ID"="V"."CUS_ID")
   6 - filter(("O"."ORD_DT">=TO_DATE(' 2024-04-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss') AND
              "O"."ORD_DT"<TO_DATE(' 2024-07-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss')))

문제 ​

  1. 3번과 4번 줄의 E-Rows 19,200이 어디서 나온 숫자인지 계산하고, 그 숫자 때문에 2번 줄에서 어떤 조인 방법을 골랐는지 설명하시오.
  2. SQL은 그대로 두고 통계로 추정을 바로잡으시오. 바로잡은 뒤의 계획과 읽는 양도 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자