테마
정산팀의 「주문 관리」는 전체 주문을 최신순(ORD_DT DESC, ORD_SEQ DESC)으로 한 쪽에 20건씩 보여 준다. 「VIP(A 등급) 주문만」을 켜면 1쪽이 0.2초, 10쪽이 2초쯤 걸린다. 정산팀은 쪽 번호를 그대로 쓰고 싶어 한다.
조민우가 "고객 조인을 쪽을 자른 다음으로 옮겼다"며 SQL 2를 올렸는데, 빨라진 대신 1쪽에 주문이 한두 건만 나오거나 아예 비었다. 오과장은 두 SQL을 비교하고 제대로 된 안을 내라고 했다.
T_ORD_BIG: 1,000,000행 · 5,826블록 · 고객당 약 500건 · 같은 날짜의 주문도, 한 고객의 주문도 테이블 곳곳에 흩어져 있다.M_CUS: 2,000행 · 30블록 · PK_M_CUS (CUS_ID) · CUS_GD A 1%(20명) · B 9% · C 90%. A 등급 고객의 주문은 약 10,000건(1%)이다.IX_ORD_BIG_DT_SEQ (ORD_DT, ORD_SEQ)(교재 09화) · IX_ORD_BIG_CUS_DT (CUS_ID, ORD_DT)(CS 화면에서 고객 한 명의 기간 조회에 쓴다).T_ORD_BIG.CUS_ID → M_CUS가 켜져 있다. 통계는 최신이고 CUS_GD에 히스토그램은 없다.-- SQL 1 : 지금 운영 중 (VIP 켜짐)
SELECT *
FROM (SELECT ROWNUM rn, a.*
FROM (SELECT o.ORD_SEQ, o.ORD_DT, o.CUS_ID, c.CUS_NM, o.ORD_AMT
FROM T_ORD_BIG o, M_CUS c
WHERE c.CUS_ID = o.CUS_ID
AND c.CUS_GD = 'A'
ORDER BY o.ORD_DT DESC, o.ORD_SEQ DESC) a
WHERE ROWNUM <= :page * 20)
WHERE rn > (:page - 1) * 20;
-- SQL 2 : 조민우가 올린 안
SELECT x.ORD_SEQ, x.ORD_DT, x.CUS_ID, c.CUS_NM, x.ORD_AMT
FROM (SELECT *
FROM (SELECT ROWNUM rn, a.*
FROM (SELECT ORD_SEQ, ORD_DT, CUS_ID, ORD_AMT
FROM T_ORD_BIG
ORDER BY ORD_DT DESC, ORD_SEQ DESC) a
WHERE ROWNUM <= :page * 20)
WHERE rn > (:page - 1) * 20) x,
M_CUS c
WHERE c.CUS_ID = x.CUS_ID
AND c.CUS_GD = 'A'
ORDER BY x.rn;
[SQL 1 · 1쪽] (10쪽은 같은 모양 · 6번 줄 A-Rows 20,130 · Buffers 약 40,400 · 2.05초)
-----------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-----------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 20 |00:00:00.21 | 4049 |
|* 1 | VIEW | | 1 | 20 | 20 |00:00:00.21 | 4049 |
|* 2 | COUNT STOPKEY | | 1 | | 20 |00:00:00.21 | 4049 |
| 3 | VIEW | | 1 | 333K | 20 |00:00:00.21 | 4049 |
| 4 | NESTED LOOPS | | 1 | 333K | 20 |00:00:00.21 | 4049 |
| 5 | NESTED LOOPS | | 1 | 1000K| 2013 |00:00:00.19 | 2049 |
| 6 | TABLE ACCESS BY INDEX ROWID| T_ORD_BIG | 1 | 1000K| 2013 |00:00:00.18 | 2020 |
| 7 | INDEX FULL SCAN DESCENDING| IX_ORD_BIG_DT_SEQ | 1 | 1000K| 2013 |00:00:00.01 | 9 |
|* 8 | INDEX UNIQUE SCAN | PK_M_CUS | 2013 | 1 | 2013 |00:00:00.01 | 29 |
|* 9 | TABLE ACCESS BY INDEX ROWID | M_CUS | 2013 | 1 | 20 |00:00:00.02 | 2000 |
-----------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("RN">(:PAGE-1)*20)
2 - filter(ROWNUM<=:PAGE*20)
8 - access("C"."CUS_ID"="O"."CUS_ID")
9 - filter("C"."CUS_GD"='A')
[SQL 2 · 1쪽]
-------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 0 |00:00:00.01 | 45 |
| 1 | SORT ORDER BY | | 1 | 7 | 0 |00:00:00.01 | 45 |
| 2 | NESTED LOOPS | | 1 | 7 | 0 |00:00:00.01 | 45 |
| 3 | NESTED LOOPS | | 1 | 20 | 20 |00:00:00.01 | 25 |
|* 4 | VIEW | | 1 | 20 | 20 |00:00:00.01 | 23 |
|* 5 | COUNT STOPKEY | | 1 | | 20 |00:00:00.01 | 23 |
| 6 | VIEW | | 1 | 1000K| 20 |00:00:00.01 | 23 |
| 7 | TABLE ACCESS BY INDEX ROWID | T_ORD_BIG | 1 | 1000K| 20 |00:00:00.01 | 23 |
| 8 | INDEX FULL SCAN DESCENDING | IX_ORD_BIG_DT_SEQ | 1 | 20 | 20 |00:00:00.01 | 3 |
|* 9 | INDEX UNIQUE SCAN | PK_M_CUS | 20 | 1 | 20 |00:00:00.01 | 2 |
|* 10 | TABLE ACCESS BY INDEX ROWID | M_CUS | 20 | 1 | 0 |00:00:00.01 | 20 |
-------------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
4 - filter("RN">(:PAGE-1)*20)
5 - filter(ROWNUM<=:PAGE*20)
9 - access("C"."CUS_ID"="X"."CUS_ID")
10 - filter("C"."CUS_GD"='A')