Skip to content
튜닝 연습인덱스 사용 조건
IDX-048

결제 개편 뒤 미결제 주문을 찾는 화면과 배치 셋이 모두 주문 테이블을 처음부터 읽는다

실전DDL한빛유통48 / 700

상황 ​

결제 연동 개편 뒤로 미결제 주문(PAY_DT가 NULL)을 읽는 SQL이 늘었다. 조민우는 SQL마다 인덱스를 하나씩, 모두 셋을 만들겠다는 요청서를 올렸다. S1용 (PAY_DT, 0), S2용 (PAY_DT, ORD_DT), S3용 (CUS_ID, PAY_DT)다. 박팀장은 요청서를 돌려보내며 이렇게 적었다. "T_ORD는 결제 때마다 UPDATE되는 테이블이다. 지금 있는 PAY_DT 인덱스 하나를 바꿔서 넷을 다 받쳐 와라."

테이블과 인덱스 ​

  • T_ORD 주문: 100,000행 · 896블록 · PK_T_ORD (ORD_SEQ) · CUS_ID VARCHAR2(40) NOT NULL(고객 2,000명, 고객당 50건) · ORD_DT DATE(NOT NULL 제약 없음, 실제 NULL 0건) · ORD_ST · PAY_TP · PAY_AMT
  • PAY_DT DATE: 결제 시각 · 결제 전이면 NULL · 지금 미결제 41건(그중 3일이 지난 PENDING 9건) · 하루 결제 약 137건 · 한 고객의 미결제는 보통 0 ~ 1건 (문제 설정: IDX-004와 같다. 2024.12 결제 연동 개편 뒤의 모습이라고 본다)
  • 인덱스 IX_T_ORD_DT (ORD_DT): 리프 265 · IX_T_ORD_PAY (PAY_DT): BLEVEL 1 · 리프 265 · 통계 최신

원본 SQL ​

sql
-- S1 「미결제 독촉」 알림 · 10분마다
SELECT ORD_SEQ, CUS_ID, ORD_DT, ORD_AMT
  FROM T_ORD
 WHERE PAY_DT IS NULL;

-- S2 「자동 취소」 배치 · 매시 정각 · 3일 지난 미결제를 취소한다
UPDATE T_ORD
   SET ORD_ST = 'CANCEL'
 WHERE PAY_DT IS NULL
   AND ORD_ST = 'PENDING'
   AND ORD_DT < TRUNC(SYSDATE) - 3;

-- S3 상담 화면 「이 고객의 미결제」 · 하루 약 3천 번
SELECT ORD_SEQ, ORD_DT, ORD_AMT
  FROM T_ORD
 WHERE CUS_ID = :v_cus
   AND PAY_DT IS NULL;

-- S4 결제팀 「일 결제 현황」 · 수시로 · 포인트 결제는 뺀다
SELECT PAY_TP, COUNT(*) AS cnt, SUM(PAY_AMT) AS amt
  FROM T_ORD
 WHERE PAY_DT >= TO_DATE(:v_dt, 'YYYYMMDD')
   AND PAY_DT <  TO_DATE(:v_dt, 'YYYYMMDD') + 1
   AND PAY_TP <> 'POINT'
 GROUP BY PAY_TP;

실행계획 학습용 예시 ​

xplan
[S1]
------------------------------------------------------------------------------------------------------------
| Id  | Operation                           | Name         | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                    |              |      1 |        |     41 |00:00:00.01 |     896 |
|*  1 |  TABLE ACCESS FULL                  | T_ORD        |      1 |     41 |     41 |00:00:00.01 |     896 |
------------------------------------------------------------------------------------------------------------
   1 - filter("PAY_DT" IS NULL)

[S2]
------------------------------------------------------------------------------------------------------------
| Id  | Operation                           | Name         | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
------------------------------------------------------------------------------------------------------------
|   0 | UPDATE STATEMENT                    |              |      1 |        |      0 |00:00:00.01 |     916 |
|   1 |  UPDATE                             | T_ORD        |      1 |        |      0 |00:00:00.01 |     916 |
|*  2 |   TABLE ACCESS FULL                 | T_ORD        |      1 |      1 |      9 |00:00:00.01 |     896 |
------------------------------------------------------------------------------------------------------------
   2 - filter(("PAY_DT" IS NULL AND "ORD_ST"='PENDING' AND "ORD_DT"<TRUNC(SYSDATE@!)-3))

[S3 · CUS_000777]
------------------------------------------------------------------------------------------------------------
| Id  | Operation                           | Name         | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                    |              |      1 |        |      1 |00:00:00.01 |     896 |
|*  1 |  TABLE ACCESS FULL                  | T_ORD        |      1 |      1 |      1 |00:00:00.01 |     896 |
------------------------------------------------------------------------------------------------------------
   1 - filter(("PAY_DT" IS NULL AND "CUS_ID"=:V_CUS))

[S4 · 2024-06-14]
------------------------------------------------------------------------------------------------------------
| Id  | Operation                           | Name         | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                    |              |      1 |        |      2 |00:00:00.01 |     141 |
|   1 |  HASH GROUP BY                      |              |      1 |      2 |      2 |00:00:00.01 |     141 |
|*  2 |   TABLE ACCESS BY INDEX ROWID BATCHED| T_ORD       |      1 |     91 |     91 |00:00:00.01 |     141 |
|*  3 |    INDEX RANGE SCAN                 | IX_T_ORD_PAY |      1 |    137 |    137 |00:00:00.01 |       2 |
------------------------------------------------------------------------------------------------------------
   2 - filter("PAY_TP"<>'POINT')
   3 - access("PAY_DT">=TO_DATE(:V_DT,'YYYYMMDD') AND "PAY_DT"<TO_DATE(:V_DT,'YYYYMMDD')+1)

문제 ​

  1. S1 ~ S3이 IX_T_ORD_PAY를 쓰지 못하는 이유와, S4는 그 인덱스를 쓰는 이유를 설명하시오.
  2. 조민우가 요청한 인덱스 셋의 문제를 각각 설명하시오. 특히 (PAY_DT, ORD_DT)가 S1을 받치지 못하는 이유를 설명하시오.
  3. IX_T_ORD_PAY 하나를 바꿔 넷을 받치는 DDL을 작성하고, S3에서 PAY_DT IS NULL과 CUS_ID = :v_cus가 각각 어떤 조건이 되는지와 반영 절차를 설명하시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자