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

상품 주문 내역이 1쪽만 나오고 2쪽부터는 빈 화면인데, 빈 화면이 1쪽보다 늦게 뜬다

중급SELECT한빛유통316 / 700

상황 ​

상품관리팀의 「상품 주문 내역」은 상품 하나의 주문 줄을 최근 주문부터 한 쪽에 10줄씩 보여 준다. 지난달 이 화면을 회사 공통 페이징 함수로 옮긴 뒤로 "1쪽은 나오는데 2쪽을 누르면 빈 화면이 뜬다"는 말이 나왔다. 조민우가 재 보니 빈 화면이 1쪽보다 오히려 오래 걸린다. 공통 페이징 함수는 원래 SQL을 인라인 뷰로 감싸고 바깥에 WHERE ROWNUM BETWEEN :lo AND :hi를 붙인다. 1쪽은 1 ~ 10, 2쪽은 11 ~ 20이다. 상품관리팀과 고객센터가 하루 약 6천 번 연다.

테이블과 인덱스 ​

  • T_ORD_DET 주문상세: 200,000행 · 896블록 · ORD_SEQ · ORD_DET_NO · ITM_ID · ORD_QTY · UNT_PRC · PK_T_ORD_DET (ORD_SEQ, ORD_DET_NO) · 상품 100개, 상품마다 약 2,000줄
  • 주문번호 순으로 쌓여 있어 상품 하나의 줄은 테이블 곳곳의 블록에 흩어져 있다. 주문번호는 주문이 들어온 순서대로 매긴다.
  • 인덱스 IX_T_ORD_DET_ITM (ITM_ID, ORD_SEQ, ORD_DET_NO): 이 화면용 · BLEVEL 1 · 리프 약 560블록 · 아래 계획은 주문 줄이 1,987줄인 상품이다. 통계는 최신이다.

원본 SQL ​

sql
SELECT ORD_SEQ, ORD_DET_NO, ORD_QTY, UNT_PRC
  FROM (SELECT ORD_SEQ, ORD_DET_NO, ORD_QTY, UNT_PRC
          FROM T_ORD_DET
         WHERE ITM_ID = :itm
         ORDER BY ORD_SEQ DESC, ORD_DET_NO DESC)
 WHERE ROWNUM BETWEEN :lo AND :hi      -- 공통 페이징 함수가 붙인다 (2쪽 = 11 ~ 20)

실행계획 학습용 예시 · 계획의 모양(COUNT STOPKEY 아래 FILTER)은 교재 환경 23ai에서 확인 ​

xplan
[1쪽 · :lo = 1, :hi = 10]
----------------------------------------------------------------------------------------------------------------
| Id  | Operation                         | Name             | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
----------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                  |                  |      1 |        |     10 |00:00:00.01 |      12 |
|*  1 |  COUNT STOPKEY                    |                  |      1 |        |     10 |00:00:00.01 |      12 |
|*  2 |   FILTER                          |                  |      1 |        |     10 |00:00:00.01 |      12 |
|   3 |    VIEW                           |                  |      1 |     10 |     10 |00:00:00.01 |      12 |
|   4 |     TABLE ACCESS BY INDEX ROWID   | T_ORD_DET        |      1 |   2000 |     10 |00:00:00.01 |      12 |
|*  5 |      INDEX RANGE SCAN DESCENDING  | IX_T_ORD_DET_ITM |      1 |     10 |     10 |00:00:00.01 |       2 |
----------------------------------------------------------------------------------------------------------------

[2쪽 · :lo = 11, :hi = 20]
----------------------------------------------------------------------------------------------------------------
| Id  | Operation                         | Name             | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
----------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                  |                  |      1 |        |      0 |00:00:00.02 |     804 |
|*  1 |  COUNT STOPKEY                    |                  |      1 |        |      0 |00:00:00.02 |     804 |
|*  2 |   FILTER                          |                  |      1 |        |      0 |00:00:00.02 |     804 |
|   3 |    VIEW                           |                  |      1 |     20 |   1987 |00:00:00.02 |     804 |
|   4 |     TABLE ACCESS BY INDEX ROWID   | T_ORD_DET        |      1 |   2000 |   1987 |00:00:00.02 |     804 |
|*  5 |      INDEX RANGE SCAN DESCENDING  | IX_T_ORD_DET_ITM |      1 |     20 |   1987 |00:00:00.01 |       7 |
----------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter(ROWNUM<=:HI)
   2 - filter(ROWNUM>=:LO)
   5 - access("ITM_ID"=:ITM)

문제 ​

  1. 2쪽이 언제나 0건인 까닭을 ROWNUM이 매겨지는 방식과 2번 줄 FILTER로 설명하시오.
  2. 0건을 돌려주는 2쪽이 10건을 돌려주는 1쪽보다 더 많이 읽는 까닭을 1번 줄 COUNT STOPKEY와 3번 줄 A-Rows로 설명하시오.
  3. 몇 쪽이든 그 쪽의 10줄이 나오고, 그 쪽까지만 읽고 멈추도록 SQL을 고치시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자