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

이상 판매 점검의 「수량 상위 1%」는 인덱스를 끝까지 읽고, 「수량 상위 20줄」에는 1,399줄이 뜬다

중급SELECT유통339 / 700

상황 ​

본사 감사팀의 「이상 판매 점검」 화면에는 위젯이 둘 있다. 하나는 판매 수량이 큰 판매 줄의 상위 1%를 내려받는 「수량 상위 1%」이고, 다른 하나는 「수량 상위 20줄」이다. 개발자는 둘 다 FETCH FIRST로 짰고, "같은 수량은 빠짐없이 보여 달라"는 말에 20줄 위젯에는 WITH TIES를 붙였다. 점검 화면은 10분마다 새로 고친다. DBA는 1% 위젯이 정렬 순서 그대로인 인덱스가 있는데도 그 인덱스를 끝까지 읽는 것을 찾았고, 감사팀은 20줄 위젯에 1,399줄이 떠서 화면이 밀린다고 한다.

테이블과 인덱스 ​

  • CF11 판매상품: 1,096,708행 · 8,204블록 · SLE_QTY NUMBER NOT NULL · CF11_PK (SLE_NO, ITM_NO)
  • 인덱스 CF11_X03 (SLE_QTY, SLE_NO, ITM_NO): 이 점검용으로 만들었다 · 위젯이 고르는 칸이 모두 들어 있다 · BLEVEL 2 · 리프 약 4,700블록(한 블록에 약 230개)
  • 수량은 98%가 1 ~ 3이다. 단체 주문은 10, 20, 30처럼 끊어서 들어와 같은 수량이 수백 ~ 수천 줄이다. 수량 30을 넘는 줄은 19줄이고 모두 수량이 다르다. 수량 30은 1,380줄이다.
  • 통계는 최신이다(NUM_ROWS 1,096,708).

원본 SQL ​

sql
-- W1 「수량 상위 1%」 (내려받기)
SELECT SLE_NO, ITM_NO, SLE_QTY
  FROM CF11
 ORDER BY SLE_QTY DESC, SLE_NO DESC, ITM_NO DESC
 FETCH FIRST 1 PERCENT ROWS ONLY;

-- W2 「수량 상위 20줄」 (같은 수량은 빠짐없이)
SELECT SLE_NO, ITM_NO, SLE_QTY
  FROM CF11
 ORDER BY SLE_QTY DESC
 FETCH FIRST 20 ROWS WITH TIES;

실행계획 학습용 예시 · PERCENT와 WITH TIES의 계획 모양은 교재 환경 23ai에서 확인 ​

xplan
[W1]
-----------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                    | Name     | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |
-----------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |          |      1 |        |  10968 |00:00:00.93 |    4703 |       |       |          |
|*  1 |  VIEW                        |          |      1 |   1096K|  10968 |00:00:00.93 |    4703 |       |       |          |
|   2 |   WINDOW BUFFER              |          |      1 |   1096K|   1096K|00:00:00.88 |    4703 |    66M|  2958K|   59M (0)|
|   3 |    INDEX FULL SCAN DESCENDING| CF11_X03 |      1 |   1096K|   1096K|00:00:00.21 |    4703 |       |       |          |
-----------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter("from$_subquery$_002"."rowlimit_$$_rownumber"<=CEIL("from$_subquery$_002"."rowlimit_$$_total"*1/100))

[W2]
----------------------------------------------------------------------------------------------------
| Id  | Operation                    | Name     | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
----------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |          |      1 |        |   1399 |00:00:00.01 |      10 |
|*  1 |  VIEW                        |          |      1 |   1096K|   1399 |00:00:00.01 |      10 |
|*  2 |   WINDOW NOSORT STOPKEY      |          |      1 |   1096K|   1400 |00:00:00.01 |      10 |
|   3 |    INDEX FULL SCAN DESCENDING| CF11_X03 |      1 |   1096K|   1400 |00:00:00.01 |      10 |
----------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter("from$_subquery$_002"."rowlimit_$$_rank"<=20)
   2 - filter(RANK() OVER ( ORDER BY "SLE_QTY" DESC )<=20)

문제 ​

  1. W1이 정렬 순서 그대로인 인덱스를 읽으면서도 끝까지 읽는 까닭을 1번 줄의 조건과 2번 줄 WINDOW BUFFER로 설명하시오.
  2. W2가 20줄이 아니라 1,399줄을 돌려준 까닭과, 그만큼 읽고서야 멈춘 까닭을 설명하시오.
  3. 두 위젯을 고치고, PERCENT와 WITH TIES를 각각 언제 써도 되는지 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자