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

POS 개편 전 점검에서 판매 SQL 넷 중 셋이 판매 전체나 인덱스 전체를 읽는다

실전DDL유통19 / 700

상황 ​

유통 POS 개편을 앞두고 박팀장이 판매(CF10)를 읽는 운영 SQL 넷을 넘기며 말했다. "인덱스는 하나만 더 늘릴 수 있다. 가능하면 늘리지 말고. SQL마다 무엇을 고칠지, 인덱스는 무엇으로 할지 정리해 와." 조회 빈도는 각 SQL 위의 주석에 적혀 있다.

테이블과 인덱스 ​

  • CF10 판매: 500,000행 · 4,583블록(전체 스캔 4,510, 블록당 약 109행) · 모든 컬럼에 NOT NULL 제약
  • SLE_NO VARCHAR2(15) '20230601-0001'(앞 8자리가 SLE_DT와 전부 같다, 공백이 든 값 0건) · SLE_DT VARCHAR2(8) · SHP_NO NUMBER(5) 1001 ~ 1005
  • 분포: 하루 596 ~ 793건(2023-06-01은 706건, 그중 매장 1003은 146건) · 2023-06-05 ~ 06-11은 4,919건 · 2023년 6월 20,953건(매장마다 4,157 ~ 4,234건) · 하루치 판매는 테이블 약 26블록 안에 모여 있다
  • 인덱스 CF10_PK (SLE_NO): BLEVEL 2 · 리프 약 1,680 / IX_CF10_SLE_DT (SLE_DT): BLEVEL 2 · 리프 1,393 · CF 9,921 · 통계 최신

원본 SQL ​

sql
-- S1 매장 「오늘 영수증 목록」: 매장 POS 5대가 수시로 (하루 약 2,000번)
SELECT SLE_NO, SLE_HMS
  FROM CF10
 WHERE TO_CHAR(SHP_NO) = :v_shp
   AND SUBSTR(SLE_NO, 1, 8) = :v_dt
 ORDER BY SLE_NO;

-- S2 본사 「주간 일별 건수」: 앱이 날짜를 숫자(20230605)로 넘긴다 (하루 약 300번)
SELECT SLE_DT, COUNT(*)
  FROM CF10
 WHERE TO_NUMBER(SLE_DT) BETWEEN :n_from AND :n_to
 GROUP BY SLE_DT;

-- S3 고객센터 「영수증 조회」: 상담원이 붙여 넣은 번호에 공백이 섞여 온다 (하루 약 1,500번)
SELECT SLE_NO, SLE_DT, SLE_HMS, SHP_NO
  FROM CF10
 WHERE TRIM(SLE_NO) = :v_no;

-- S4 본사 「월 매장별 건수」 (하루 약 200번)
SELECT SHP_NO, COUNT(*)
  FROM CF10
 WHERE SLE_DT LIKE :v_ym || '%'
 GROUP BY SHP_NO;

실행계획 학습용 예시 · 건수는 적재 데이터 그대로 ​

xplan
[S1 — 매장 1003 · 2023-06-01]
-----------------------------------------------------------------------------------
| Id  | Operation          | Name | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
-----------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |      1 |        |    146 |00:00:00.05 |    4510 |
|   1 |  SORT ORDER BY     |      |      1 |     50 |    146 |00:00:00.05 |    4510 |
|*  2 |   TABLE ACCESS FULL| CF10 |      1 |     50 |    146 |00:00:00.05 |    4510 |
-----------------------------------------------------------------------------------
   2 - filter((TO_CHAR("SHP_NO")=:V_SHP AND SUBSTR("SLE_NO",1,8)=:V_DT))

[S2 — 2023-06-05 ~ 06-11]
-----------------------------------------------------------------------------------------------
| Id  | Operation             | Name           | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
-----------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |                |      1 |        |      7 |00:00:00.04 |    1406 |
|   1 |  HASH GROUP BY        |                |      1 |    730 |      7 |00:00:00.04 |    1406 |
|*  2 |   INDEX FAST FULL SCAN| IX_CF10_SLE_DT |      1 |   1250 |   4919 |00:00:00.03 |    1406 |
-----------------------------------------------------------------------------------------------
   2 - filter((TO_NUMBER("SLE_DT")>=:N_FROM AND TO_NUMBER("SLE_DT")<=:N_TO))

[S3]
-----------------------------------------------------------------------------------
| Id  | Operation          | Name | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
-----------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |      1 |        |      1 |00:00:00.04 |    4510 |
|*  1 |  TABLE ACCESS FULL | CF10 |      1 |   5000 |      1 |00:00:00.04 |    4510 |
-----------------------------------------------------------------------------------
   1 - filter(TRIM("SLE_NO")=:V_NO)

[S4 — 2023-06]
-------------------------------------------------------------------------------------------------------------
| Id  | Operation                            | Name           | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
-------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                     |                |      1 |        |      5 |00:00:00.02 |     478 |
|   1 |  HASH GROUP BY                       |                |      1 |      5 |      5 |00:00:00.02 |     478 |
|   2 |   TABLE ACCESS BY INDEX ROWID BATCHED| CF10           |      1 |    693 |  20953 |00:00:00.02 |     478 |
|*  3 |    INDEX RANGE SCAN                  | IX_CF10_SLE_DT |      1 |    693 |  20953 |00:00:00.01 |      62 |
-------------------------------------------------------------------------------------------------------------
   3 - access("SLE_DT" LIKE :V_YM||'%')
       filter("SLE_DT" LIKE :V_YM||'%')

문제 ​

  1. S1 ~ S4 각각에서 컬럼이 어떻게 가공되었고 그 때문에 실행계획이 어떻게 되었는지 설명하시오. 고칠 필요가 없는 SQL이 있다면 그 이유도 설명하시오.
  2. 결과가 바뀌지 않게 SQL을 고치고, 인덱스를 늘리지 않거나 하나만 바꿔 네 SQL을 받치는 인덱스 설계를 DDL로 제안하시오.
  3. 제안한 인덱스 변경의 대가와 반영 전에 확인할 것을 설명하시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자