Skip to content
튜닝 연습조인 튜닝
JOIN-059

매장 계약 이력을 붙이는 SQL 넷에 인덱스를 달아 달라는데 이력 테이블은 6블록뿐이다

실전DDL버거209 / 700

상황 ​

본사는 매장의 계약 형태(직영, 가맹), 점주, 수수료율이 바뀔 때마다 매장 계약 이력에 새 구간을 연다. 이 이력을 붙이는 SQL 네 개가 새로 생겼고, 개발팀은 "이력 조인이 다 느릴 테니 이력 테이블에 인덱스를 두세 개 달아 달라"는 요청서를 올렸다. 박팀장은 "주문 테이블에는 POS가 실시간으로 넣으니 인덱스는 하나까지만 허락한다. 네 SQL을 다 보고 꼭 필요한 것만 정해 달라"라고 했다. 지금 느리다고 민원이 들어온 것은 경영진 보고 기간에 하루 50번쯤 도는 Q3뿐이다.

테이블과 인덱스 ​

  • ORD 주문: 1,000,000행 · 22,422블록 · ORD_PK (ORD_NO) · ORD_X01 (ORD_DT, ORD_HMS) · 날짜순 적재(하루 약 1,370건이 약 31블록에 모여 있다) · SHOP_NO 200곳, 매장마다 하루 약 6.8건
  • SHOP_CTRT_HIS 매장 계약 이력 (이 문제에서 쓰는 파생 테이블, 선분이력): SHOP_NO · STA_DT · END_DT (VARCHAR2(8), 그날까지 포함, 지금 구간은 '99991231') · CTRT_TP · OWNER_NM · FEE_RT · CHG_RSN (신규, 가맹전환, 점주변경) · 612행 · 6블록 · SHOP_CTRT_HIS_PK (SHOP_NO, STA_DT) · 매장마다 평균 3구간
  • 2013년에 직영에서 가맹으로 바뀐 매장은 12곳이다 · 구간은 겹치거나 끊기지 않는다 · 통계 최신

원본 SQL ​

sql
-- [Q1] 점주 앱 첫 화면: 내 매장의 지금 계약 (하루 2만 번)
SELECT CTRT_TP, OWNER_NM, FEE_RT
  FROM SHOP_CTRT_HIS
 WHERE SHOP_NO = :shop
   AND :today BETWEEN STA_DT AND END_DT;

-- [Q2] 월 가맹 수수료 정산: 주문 당시 계약의 수수료율로 (월 1회)
SELECT o.SHOP_NO, h.CTRT_TP, h.FEE_RT, COUNT(*) AS ord_cnt
  FROM ORD o, SHOP_CTRT_HIS h
 WHERE o.ORD_DT BETWEEN :v_from AND :v_to
   AND h.SHOP_NO = o.SHOP_NO
   AND o.ORD_DT BETWEEN h.STA_DT AND h.END_DT
 GROUP BY o.SHOP_NO, h.CTRT_TP, h.FEE_RT;

-- [Q3] 가맹 전환 효과: 전환한 매장의 전환 전 30일과 후 30일 주문 수 (보고 기간 하루 50번)
SELECT h.SHOP_NO, h.STA_DT AS CHG_DT,
       COUNT(CASE WHEN o.ORD_DT <  h.STA_DT THEN 1 END) AS before_30d,
       COUNT(CASE WHEN o.ORD_DT >= h.STA_DT THEN 1 END) AS after_30d
  FROM SHOP_CTRT_HIS h, ORD o
 WHERE h.CHG_RSN = '가맹전환'
   AND o.SHOP_NO = h.SHOP_NO
   AND o.ORD_DT >= TO_CHAR(TO_DATE(h.STA_DT, 'YYYYMMDD') - 30, 'YYYYMMDD')
   AND o.ORD_DT <  TO_CHAR(TO_DATE(h.STA_DT, 'YYYYMMDD') + 30, 'YYYYMMDD')
 GROUP BY h.SHOP_NO, h.STA_DT;

-- [Q4] 계약 이력 화면: 매장 하나의 전체 이력 (하루 수십 번)
SELECT STA_DT, END_DT, CTRT_TP, OWNER_NM, FEE_RT
  FROM SHOP_CTRT_HIS
 WHERE SHOP_NO = :shop
 ORDER BY STA_DT;

실행계획 학습용 예시 ​

xplan
[Q1] Buffers 5 (SHOP_CTRT_HIS_PK 범위 2 + 테이블 3) · [Q4] Buffers 5
[Q2] 2013년 7월 · ORD_X01 범위(42,408건) + 이력 FULL 6 → HASH JOIN · Buffers 1,082 · 0.09초 (월 1회)

[Q3]
-----------------------------------------------------------------------------------------------------
| Id  | Operation           | Name          | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
-----------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT    |               |      1 |        |     12 |00:00:00.93 |   22428 |
|   1 |  HASH GROUP BY      |               |      1 |     12 |     12 |00:00:00.93 |   22428 |
|*  2 |   HASH JOIN         |               |      1 |   1250 |   4896 |00:00:00.92 |   22428 |
|*  3 |    TABLE ACCESS FULL| SHOP_CTRT_HIS |      1 |     12 |     12 |00:00:00.01 |       6 |
|   4 |    TABLE ACCESS FULL| ORD           |      1 |   1000K|   1000K|00:00:00.21 |   22422 |
-----------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - access("O"."SHOP_NO"="H"."SHOP_NO")
       filter(("O"."ORD_DT">=TO_CHAR(TO_DATE("H"."STA_DT",'YYYYMMDD')-30,'YYYYMMDD') AND
              "O"."ORD_DT"<TO_CHAR(TO_DATE("H"."STA_DT",'YYYYMMDD')+30,'YYYYMMDD')))
   3 - filter("H"."CHG_RSN"='가맹전환')

문제 ​

  1. 네 SQL 각각에 대해 지금 일량이 어디서 나오는지 쓰고, 이력 테이블에 인덱스를 더 만들면 어느 SQL이 얼마나 나아지는지 판단하시오.
  2. Q3를 빠르게 하려면 어느 테이블에서 출발해야 하는지 정하고, 그때 필요한 인덱스를 DDL로 작성하시오. 고친 Q3의 일량을 숫자로 쓰시오.
  3. 새 인덱스의 대가와 그 인덱스가 함께 덕을 볼 수 있는 화면을 쓰고, 만들지 않을 인덱스와 그 이유를 정리하시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자