Skip to content
튜닝 연습소트 튜닝과 페이징
SORT-013

많이 팔리는 메뉴는 「최근 판매 50건」이 바로 뜨는데 시즌 메뉴를 고르면 화면이 멈춘다

중급SELECT유통318 / 700

상황 ​

본사 메뉴팀의 「메뉴별 최근 판매」 화면은 메뉴를 고르면 그 메뉴가 담긴 최근 판매 50줄(판매번호, 수량, 단가)을 보여 준다. (ICE)아메리카노를 고르면 바로 뜨는데, 2022년 겨울에만 팔고 내린 시즌 메뉴를 고르면 2초 가까이 걸린다. 개발자는 "둘 다 인덱스를 탔고 실행계획 모양도 똑같다"라고 한다. 메뉴팀은 신메뉴를 등록한 첫날에도 이 화면을 자주 연다.

테이블과 인덱스 ​

  • CF11 판매상품: 1,096,708행 · 8,204블록 · SLE_NO VARCHAR2(15) '20231231-0612'(판매일 + 그날 일련번호, 판매 순서대로 매겨진다) · 판매번호 순서대로 적재되어 있다
  • CF11_PK (SLE_NO, ITM_NO): BLEVEL 2 · 리프 약 4,290블록 · 다른 인덱스 없음 · 통계 최신(ITM_NO 히스토그램 없음, 팔린 메뉴 88종)
  • (ICE)아메리카노는 275,135줄(25%)이다. 시즌 메뉴 하나는 2022년 12월 ~ 2023년 2월에만 팔려 3,032줄이고 마지막 판매는 2023-02-28이다. 판매 데이터는 2023-12-31까지다
  • 인덱스의 오래된 구간은 버퍼 캐시에 거의 없어서 디스크에서 한 블록씩 읽는다

원본 SQL ​

sql
SELECT SLE_NO, SLE_QTY, SLE_UPRC
  FROM CF11
 WHERE ITM_NO = :itm
 ORDER BY SLE_NO DESC
 FETCH FIRST 50 ROWS ONLY

실행계획 학습용 예시 ​

xplan
[(ICE)아메리카노]
----------------------------------------------------------------------------------------------------------
| Id  | Operation                     | Name    | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads |
----------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |         |      1 |        |     50 |00:00:00.01 |       5 |     0 |
|*  1 |  VIEW                         |         |      1 |     50 |     50 |00:00:00.01 |       5 |     0 |
|*  2 |   WINDOW NOSORT STOPKEY       |         |      1 |     50 |     50 |00:00:00.01 |       5 |     0 |
|   3 |    TABLE ACCESS BY INDEX ROWID| CF11    |      1 |  12463 |     50 |00:00:00.01 |       5 |     0 |
|*  4 |     INDEX FULL SCAN DESCENDING| CF11_PK |      1 |     50 |     50 |00:00:00.01 |       3 |     0 |
----------------------------------------------------------------------------------------------------------

[시즌 메뉴 · 마지막 판매 2023-02-28]
----------------------------------------------------------------------------------------------------------
| Id  | Operation                     | Name    | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads |
----------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |         |      1 |        |     50 |00:00:01.62 |    1827 |  1795 |
|*  1 |  VIEW                         |         |      1 |     50 |     50 |00:00:01.62 |    1827 |  1795 |
|*  2 |   WINDOW NOSORT STOPKEY       |         |      1 |     50 |     50 |00:00:01.62 |    1827 |  1795 |
|   3 |    TABLE ACCESS BY INDEX ROWID| CF11    |      1 |  12463 |     50 |00:00:01.62 |    1827 |  1795 |
|*  4 |     INDEX FULL SCAN DESCENDING| CF11_PK |      1 |     50 |     50 |00:00:01.60 |    1810 |  1790 |
----------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter("from$_subquery$_002"."rowlimit_$$_rownumber"<=50)
   2 - filter(ROW_NUMBER() OVER ( ORDER BY INTERNAL_FUNCTION("SLE_NO") DESC )<=50)
   4 - filter("ITM_NO"=:ITM)

문제 ​

  1. 두 실행계획은 모양도 같고 A-Rows도 같은데 Buffers가 5와 1,827로 갈린 까닭을 4번 줄의 조건(access인지 filter인지)으로 설명하시오. 판매가 50줄도 안 되는 신메뉴를 고르면 어떻게 되는지도 쓰시오.
  2. 어떤 메뉴를 골라도 50줄만 읽고 멈추도록 인덱스를 제안하고, 그 인덱스의 대가를 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자