테마
본사 운영팀의 「전 매장 주문 목록」은 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_NOORD_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 넷이다. 통계는 최신이다.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
[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 )
A-Rows가 20,000인 까닭을 쓰고, OFFSET이 "그 자리로 바로 건너뛴다"는 말이 맞는지 설명하시오.