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

「메뉴별 판매 내역」은 앞쪽은 바로 뜨는데 200쪽쯤부터 1초 가까이 걸린다

중급SELECT버거333 / 700

상황 ​

본사 메뉴팀의 「메뉴별 판매 내역」은 메뉴 하나의 주문상품 줄을 최신순으로 한 쪽에 50줄씩 보여 준다. 메뉴팀은 그 메뉴가 몇 달 전에 어땠는지 보려고 쪽 번호를 200, 300처럼 넣고 바로 넘어간다. 쪽 번호와 화면 모양은 다음 분기 개편 때까지 바꾸지 않기로 했다. 이번 분기에는 ORD_ITEM에 인덱스를 더 만들거나 키우는 작업도 승인되지 않는다(주문상품 적재 배치의 부담 때문이다). 아래는 메뉴 하나의 200쪽(9,951 ~ 10,000번째 줄)을 열었을 때의 실행계획이다.

테이블과 인덱스 ​

  • ORD_ITEM 주문상품: 2,472,717행 · 55,265블록 · ORD_NO · ITEM_ID · ORD_ITEM_QTY · ORD_DT · ORD_HMS · 메뉴 92개 · 메뉴 하나에 평균 약 2만 7천 줄
  • 인덱스 ORD_ITEM_X03 (ITEM_ID, ORD_DT, ORD_HMS, ORD_NO): 지난 분기 이 화면용으로 만들었다. 리프 한 블록에 약 210개 항목이 들어간다. 한 메뉴의 줄은 테이블 전체에 흩어져 있어 줄마다 다른 블록에 있다(클러스터링 팩터가 행 수에 가깝다).
  • ORD_ITEM_PK (ORD_NO, ITEM_ID)이므로 메뉴 하나 안에서는 ORD_NO가 유일하다. 오래된 블록은 대부분 버퍼 캐시에 없다. 통계는 최신이다.

원본 SQL ​

sql
SELECT ORD_NO, ORD_DT, ORD_HMS, ORD_ITEM_QTY
  FROM (SELECT ROWNUM rn, a.*
          FROM (SELECT ORD_NO, ORD_DT, ORD_HMS, ORD_ITEM_QTY
                  FROM ORD_ITEM
                 WHERE ITEM_ID = :item
                 ORDER BY ORD_DT DESC, ORD_HMS DESC, ORD_NO DESC) a
         WHERE ROWNUM <= :page * 50)
 WHERE rn > (:page - 1) * 50

실행계획 학습용 예시 ​

xplan
[200쪽 · :page = 200]   (1쪽은 같은 모양 · A-Rows 50 · Buffers 53 · 0.01초)
-------------------------------------------------------------------------------------------------------------------
| Id  | Operation                       | Name         | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads |
-------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                |              |      1 |        |     50 |00:00:00.94 |   10041 |  8873 |
|*  1 |  VIEW                           |              |      1 |  10000 |     50 |00:00:00.94 |   10041 |  8873 |
|*  2 |   COUNT STOPKEY                 |              |      1 |        |  10000 |00:00:00.94 |   10041 |  8873 |
|   3 |    VIEW                         |              |      1 |  26877 |  10000 |00:00:00.93 |   10041 |  8873 |
|   4 |     TABLE ACCESS BY INDEX ROWID | ORD_ITEM     |      1 |  26877 |  10000 |00:00:00.93 |   10041 |  8873 |
|*  5 |      INDEX RANGE SCAN DESCENDING| ORD_ITEM_X03 |      1 |  26877 |  10000 |00:00:00.01 |      51 |     0 |
-------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter("RN">(:PAGE-1)*50)
   2 - filter(ROWNUM<=:PAGE*50)
   5 - access("ITEM_ID"=:ITEM)

문제 ​

  1. 200쪽에서 4번 줄이 테이블을 10,000번 찾아가는 까닭과, 그 가운데 화면에 쓰이는 것이 몇 번인지 설명하시오.
  2. 쪽 번호와 인덱스는 그대로 두고, 버릴 줄은 테이블을 읽지 않도록 SQL을 고치시오.
  3. 고친 SQL이 의도대로 돌려면 실행계획에서 무엇을 확인하고 무엇을 고정해야 하는지 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자