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

업그레이드 다음 날부터 가맹점주 앱의 「최근 주문 20건」이 2초를 넘긴다

중급SELECT버거246 / 700

상황 ​

가맹점주 앱 첫 화면의 「최근 주문 20건」은 매장의 최근 주문 20건과 주문마다 수량 합계를 보여 준다. 점주 200명이 하루 평균 50번씩 연다. 지난 주말 DB를 11g에서 19c로 올린 다음 날부터 한 번에 2.4초가 걸린다. SQL과 인덱스는 바꾸지 않았다. 업그레이드 전 계획을 보관해 둔 오과장의 메모에는 "11g: INDEX RANGE SCAN DESCENDING, 스칼라 서브쿼리 Starts 20, Buffers 104"라고 적혀 있다.

테이블과 인덱스 ​

  • ORD: 1,000,000행 · 22,422블록 · 매장마다 약 5,000건 · ORD_X02 (SHOP_NO, ORD_DT, ORD_HMS)(이 화면용)
  • ORD_ITEM: 2,472,717행 · 55,265블록 · 주문당 1 ~ 4줄(같은 주문의 줄은 같은 블록에 있다) · ORD_ITEM_PK (ORD_NO, ITEM_ID)
  • 19c에서는 스칼라 서브쿼리 Unnesting(_optimizer_unnest_scalar_sq)이 기본으로 켜져 있다 · 통계 최신 · 아래는 :shop = 'SH0001'

원본 SQL ​

sql
SELECT *
  FROM (SELECT o.ORD_NO, o.ORD_DT, o.ORD_HMS,
               (SELECT SUM(i.ORD_ITEM_QTY)
                  FROM ORD_ITEM i
                 WHERE i.ORD_NO = o.ORD_NO) AS qty
          FROM ORD o
         WHERE o.SHOP_NO = :shop
         ORDER BY o.ORD_DT DESC, o.ORD_HMS DESC)
 WHERE ROWNUM <= 20

실행계획 학습용 예시 ​

xplan
---------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                              | Name     | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads | Used-Mem |
---------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                       |          |      1 |        |     20 |00:00:02.41 |   60310 | 55265 |          |
|*  1 |  COUNT STOPKEY                         |          |      1 |        |     20 |00:00:02.41 |   60310 | 55265 |          |
|   2 |   VIEW                                 |          |      1 |   5000 |     20 |00:00:02.41 |   60310 | 55265 |          |
|*  3 |    SORT ORDER BY STOPKEY               |          |      1 |   5000 |     20 |00:00:02.41 |   60310 | 55265 | 6144 (0) |
|*  4 |     HASH JOIN OUTER                    |          |      1 |   5000 |   5000 |00:00:02.40 |   60310 | 55265 | 1517K (0)|
|   5 |      TABLE ACCESS BY INDEX ROWID BATCHED| ORD     |      1 |   5000 |   5000 |00:00:00.02 |    5045 |     0 |          |
|*  6 |       INDEX RANGE SCAN                 | ORD_X02  |      1 |   5000 |   5000 |00:00:00.01 |      21 |     0 |          |
|   7 |      VIEW                              | VW_SSQ_1 |      1 |   1000K|   1000K|00:00:02.10 |   55265 | 55265 |          |
|   8 |       HASH GROUP BY                    |          |      1 |   1000K|   1000K|00:00:01.95 |   55265 | 55265 |   71M (0)|
|   9 |        TABLE ACCESS FULL               | ORD_ITEM |      1 |   2472K|   2472K|00:00:00.52 |   55265 | 55265 |          |
---------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter(ROWNUM<=20)
   3 - filter(ROWNUM<=20)
   4 - access("ITEM_1"(+)="O"."ORD_NO")
   6 - access("O"."SHOP_NO"=:SHOP)

문제 ​

  1. 11g의 계획과 비교해 지금 계획에서 무엇이 바뀌었는지 줄 번호로 설명하시오. 7번 줄의 VW_SSQ_1이 무엇이고, 왜 주문상품 247만 행을 다 읽는지도 쓰시오.
  2. 화면에 나가는 20건에만 수량을 구하도록 SQL을 고치시오.
  3. 옵티마이저가 왜 이 계획을 골랐는지 설명하고, 원본에 NO_UNNEST만 넣는 것으로 충분한지 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자