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

「전 매장 주문 목록」 1쪽은 바로 뜨는데 400쪽은 한참 걸린다

기초SELECT버거324 / 700

상황 ​

본사 운영팀의 「전 매장 주문 목록」은 200개 매장의 주문을 최신순(주문번호 역순)으로 한 쪽에 50건씩 보여 준다. 운영팀은 2주 전 마감분을 찾으려고 쪽 번호 칸에 400을 넣고 바로 넘어간다(하루 약 1,370건이라 400쪽이 2주 전쯤이다). 지난달 조민우가 예전 ROWNUM 페이징을 OFFSET … FETCH로 바꾸면서 이렇게 말했다. "OFFSET은 그 자리로 바로 건너뛰니까 400쪽도 1쪽처럼 빠르다." 아래는 1쪽과 400쪽을 열었을 때의 실행계획이다.

테이블과 인덱스 ​

  • ORD 주문: 1,000,000행 · 22,422블록(블록당 약 45행) · ORD_NO NUMBER · ORD_DT VARCHAR2(8) · ORD_HMS VARCHAR2(6) · SHOP_NO
  • ORD_PK (ORD_NO): 높이 3 · 리프 약 2,000블록(리프 한 블록에 약 500개). 주문번호는 주문이 들어온 순서대로 매기고 테이블도 날짜순으로 쌓여 있어, ORD_PK 순서와 테이블 순서가 거의 같다(클러스터링 팩터 약 22,400).
  • ORD_X01 (ORD_DT, ORD_HMS)도 있다.
  • 화면에 보이는 칸은 ORD_NO, ORD_DT, ORD_HMS, SHOP_NO 넷이다. 통계는 최신이다.

원본 SQL ​

sql
SELECT ORD_NO, ORD_DT, ORD_HMS, SHOP_NO
  FROM ORD
 ORDER BY ORD_NO DESC
 OFFSET :off ROWS FETCH NEXT 50 ROWS ONLY      -- :off = (쪽 번호 - 1) * 50

실행계획 학습용 예시 ​

xplan
[1쪽 · :off = 0]
-----------------------------------------------------------------------------------------------------
| Id  | Operation                      | Name   | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
-----------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT               |        |      1 |        |     50 |00:00:00.01 |       5 |
|*  1 |  VIEW                          |        |      1 |     50 |     50 |00:00:00.01 |       5 |
|*  2 |   WINDOW NOSORT STOPKEY        |        |      1 |     50 |     50 |00:00:00.01 |       5 |
|   3 |    TABLE ACCESS BY INDEX ROWID | ORD    |      1 |   1000K|     50 |00:00:00.01 |       5 |
|   4 |     INDEX FULL SCAN DESCENDING | ORD_PK |      1 |     50 |     50 |00:00:00.01 |       3 |
-----------------------------------------------------------------------------------------------------

[400쪽 · :off = 19950]
-----------------------------------------------------------------------------------------------------
| Id  | Operation                      | Name   | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
-----------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT               |        |      1 |        |     50 |00:00:00.04 |     488 |
|*  1 |  VIEW                          |        |      1 |     50 |     50 |00:00:00.04 |     488 |
|*  2 |   WINDOW NOSORT STOPKEY        |        |      1 |     50 |  20000 |00:00:00.04 |     488 |
|   3 |    TABLE ACCESS BY INDEX ROWID | ORD    |      1 |   1000K|  20000 |00:00:00.03 |     488 |
|   4 |     INDEX FULL SCAN DESCENDING | ORD_PK |      1 |     50 |  20000 |00:00:00.01 |      42 |
-----------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter(("from$_subquery$_002"."rowlimit_$$_rownumber"<=CASE  WHEN (:OFF>=0) THEN
              FLOOR(TO_NUMBER(TO_CHAR(:OFF)))+50 ELSE 50 END  AND "from$_subquery$_002"."rowlimit_$$_rownumber">:OFF))
   2 - filter(ROW_NUMBER() OVER ( ORDER BY INTERNAL_FUNCTION("ORD_NO") DESC )<=CASE  WHEN (:OFF>=0) THEN
              FLOOR(TO_NUMBER(TO_CHAR(:OFF)))+50 ELSE 50 END )

문제 ​

  1. 400쪽 실행계획의 2번 줄과 3번 줄에서 A-Rows가 20,000인 까닭을 쓰고, OFFSET이 "그 자리로 바로 건너뛴다"는 말이 맞는지 설명하시오.
  2. 몇 쪽째든 읽는 양이 비슷하도록 SQL을 고치시오. 고친 방식으로는 할 수 없게 되는 일이 있다면 그 대안도 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자