Skip to content
튜닝 연습서브쿼리와 쿼리 변환
SUBQ-009

결제 대기 목록 한 쪽 50건을 여는 데 주문상세를 150번 찾아간다

중급SELECT한빛유통244 / 700

상황 ​

정산팀의 「결제 대기 주문」 화면은 결제가 안 된 주문(PENDING)을 최근 것부터 한 쪽에 50건씩 보여 주고, 주문마다 상세 줄 수와 수량, 상세 금액 합계를 붙여 주문 금액과 대조하게 한다. 정산 담당자 10명이 하루 약 3,000번 연다. 신입 개발자는 "상세 칸이 세 개라 스칼라 서브쿼리를 세 개 붙였다. 페이징 뒤에 두었으니 50번씩만 돈다"라고 했다.

테이블과 인덱스 ​

  • T_ORD: 100,000행 · 896블록 · PENDING 8,000건 · IX_T_ORD_ST_DT (ORD_ST, ORD_DT)가 있다(이 화면용)
  • T_ORD_DET: 200,000행 · 896블록 · 주문당 1 ~ 3줄(같은 주문의 줄은 같은 블록에 있다) · PK_T_ORD_DET (ORD_SEQ, ORD_DET_NO)
  • 통계 최신 · 아래는 첫 쪽(최근 50건)

원본 SQL ​

sql
SELECT o.ORD_SEQ, o.ORD_DT, o.CUS_ID, o.ORD_AMT,
       (SELECT COUNT(*)                   FROM T_ORD_DET d WHERE d.ORD_SEQ = o.ORD_SEQ) AS det_cnt,
       (SELECT SUM(d.ORD_QTY)             FROM T_ORD_DET d WHERE d.ORD_SEQ = o.ORD_SEQ) AS det_qty,
       (SELECT SUM(d.ORD_QTY * d.UNT_PRC) FROM T_ORD_DET d WHERE d.ORD_SEQ = o.ORD_SEQ) AS det_amt
  FROM (SELECT ORD_SEQ, ORD_DT, CUS_ID, ORD_AMT
          FROM T_ORD
         WHERE ORD_ST = 'PENDING'
         ORDER BY ORD_DT DESC
         FETCH FIRST 50 ROWS ONLY) o
 ORDER BY o.ORD_DT DESC

실행계획 학습용 예시 ​

xplan
-----------------------------------------------------------------------------------------------------------------
| Id  | Operation                              | Name           | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
-----------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                       |                |      1 |        |     50 |00:00:00.01 |     453 |
|   1 |  SORT AGGREGATE                        |                |     50 |      1 |     50 |00:00:00.01 |     100 |
|*  2 |   INDEX RANGE SCAN                     | PK_T_ORD_DET   |     50 |      2 |     99 |00:00:00.01 |     100 |
|   3 |  SORT AGGREGATE                        |                |     50 |      1 |     50 |00:00:00.01 |     150 |
|   4 |   TABLE ACCESS BY INDEX ROWID BATCHED  | T_ORD_DET      |     50 |      2 |     99 |00:00:00.01 |     150 |
|*  5 |    INDEX RANGE SCAN                    | PK_T_ORD_DET   |     50 |      2 |     99 |00:00:00.01 |     100 |
|   6 |  SORT AGGREGATE                        |                |     50 |      1 |     50 |00:00:00.01 |     150 |
|   7 |   TABLE ACCESS BY INDEX ROWID BATCHED  | T_ORD_DET      |     50 |      2 |     99 |00:00:00.01 |     150 |
|*  8 |    INDEX RANGE SCAN                    | PK_T_ORD_DET   |     50 |      2 |     99 |00:00:00.01 |     100 |
|   9 |  SORT ORDER BY                         |                |      1 |     50 |     50 |00:00:00.01 |     453 |
|  10 |   VIEW                                 |                |      1 |     50 |     50 |00:00:00.01 |      53 |
|* 11 |    WINDOW NOSORT STOPKEY               |                |      1 |   8000 |     50 |00:00:00.01 |      53 |
|  12 |     TABLE ACCESS BY INDEX ROWID        | T_ORD          |      1 |   8000 |     51 |00:00:00.01 |      53 |
|* 13 |      INDEX RANGE SCAN DESCENDING       | IX_T_ORD_ST_DT |      1 |   8000 |     51 |00:00:00.01 |       2 |
-----------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - access("D"."ORD_SEQ"=:B1)
   5 - access("D"."ORD_SEQ"=:B1)
   8 - access("D"."ORD_SEQ"=:B1)
  11 - filter(ROW_NUMBER() OVER ( ORDER BY INTERNAL_FUNCTION("ORD_DT") DESC )<=50)
  13 - access("ORD_ST"='PENDING')

문제 ​

  1. 1번, 3번, 6번 줄이 각각 무엇을 하는지 쓰고, 한 쪽을 열 때 주문상세를 몇 번 찾아가는지 Starts로 계산하시오.
  2. 주문마다 주문상세를 한 번만 읽도록 SQL을 고치시오. 페이징을 지금 자리에 그대로 두어야 하는 이유도 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자