Skip to content
튜닝 연습조인 튜닝
JOIN-083

공통 모듈의 주문 상세 SQL은 계획이 바뀔 때마다 상담 화면과 회계 배치 가운데 한쪽이 느려진다

실전SELECT한빛유통233 / 700

상황 ​

공통 모듈의 「주문 상세」 SQL(PKG_ORD_COM.Q7)은 기간을 받아 주문과 주문상세를 주문번호 순으로 낸다. 상담 화면 「하루 주문 상세」가 하루치로 하루 3,000번 부르고, 회계팀의 월 배치 「연간 주문 상세 파일」이 한 달에 한 번 1년 치로 부른다. 두 곳은 SQL을 각자 복사해 써서 커서가 따로 생기고, 지금은 둘 다 힌트가 없다. 지난달 통계를 다시 모은 뒤 화면 쪽 계획이 해시로 바뀌어 화면이 느려졌다는 민원이 왔고, 작년에는 배치 쪽이 NL로 돌아 새벽 배치가 밀린 적이 있다. 조민우가 같은 SQL에 조인 방식 힌트만 바꿔 가며 두 기간을 재었다. 박팀장은 "세 방식을 비교한 의견서를 쓰고, 두 곳에 무엇을 쓸지 정해 달라"라고 했다.

테이블과 인덱스 ​

  • T_ORD 주문: 100,000행 · 896블록 · PK_T_ORD (ORD_SEQ) · IX_T_ORD_DT (ORD_DT) · ORD_SEQ 순으로 쌓였고 같은 날짜의 주문은 테이블 곳곳에 흩어져 있다(하루 137건이 137블록) · 2024년 50,000건(테이블의 절반)
  • T_ORD_DET 주문상세: 200,000행 · 896블록 · PK_T_ORD_DET (ORD_SEQ, ORD_DET_NO) (BLEVEL 1) · 주문당 1 ~ 3건(평균 2) · 주문 순서로 적재
  • pga_aggregate_target 512M · AUTO · 월 배치가 도는 새벽에는 다른 배치 다섯 개가 함께 돈다
  • 측정은 모두 LEADING(o d)로 순서를 고정하고 조인 방식 힌트만 바꿨다

원본 SQL ​

sql
SELECT o.ORD_SEQ, o.ORD_DT, o.CUS_ID, d.ORD_DET_NO, d.ITM_ID, d.ORD_QTY, d.UNT_PRC
  FROM T_ORD o, T_ORD_DET d
 WHERE o.ORD_DT >= :d1
   AND o.ORD_DT <  :d2
   AND d.ORD_SEQ = o.ORD_SEQ
 ORDER BY o.ORD_SEQ, d.ORD_DET_NO

실행계획 학습용 예시 ​

xplan
[측정 요약 · LEADING(o d) 고정, 조인 방식 힌트만 바꿈]
 기간                 방식                    Buffers   A-Time   조인 줄의 메모리                          마지막 정렬
 A 하루 (주문 137)    NL    USE_NL(d)             688   0.01초   없음                                      275줄 · 2048 (0)
                      해시  USE_HASH(d)         1,035   0.02초   HASH JOIN build 주문 137행 · 1,190K (0)    275줄 · 2048 (0)
                      머지  USE_MERGE(d)        1,035   0.14초   SORT JOIN 주문상세 20만 줄 · 8,960K (0)    275줄 · 2048 (0)
 B 1년 (주문 50,000)  NL    USE_NL(d)            201K   0.71초   없음                                      10만 줄 · 6,420K (0)
                      해시  USE_HASH(d)         1,792   0.26초   HASH JOIN build 주문 5만 행 · 3,480K (0)   10만 줄 · 6,420K (0)
                      머지  USE_MERGE(d)        1,792   0.44초   SORT JOIN 주문 5만 행 · 2,310K (0)         10만 줄 · 6,420K (0)
                                                                 + SORT JOIN 주문상세 20만 줄 · 8,960K (0)
 (A는 T_ORD를 IX_T_ORD_DT로, B는 T_ORD 절반을 읽으므로 세 방식 모두 FULL로 읽었다)

-------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                              | Name         | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Used-Mem |
-------------------------------------------------------------------------------------------------------------------------
[B 1년 · NL]
|   0 | SELECT STATEMENT                       |              |      1 |        |   100K |00:00:00.71 |    201K |          |
|   1 |  SORT ORDER BY                         |              |      1 |   100K |   100K |00:00:00.71 |    201K | 6420K (0)|
|   2 |   NESTED LOOPS                         |              |      1 |   100K |   100K |00:00:00.58 |    201K |          |
|   3 |    NESTED LOOPS                        |              |      1 |   100K |   100K |00:00:00.29 |    101K |          |
|*  4 |     TABLE ACCESS FULL                  | T_ORD        |      1 |  50000 |  50000 |00:00:00.02 |     896 |          |
|*  5 |     INDEX RANGE SCAN                   | PK_T_ORD_DET |  50000 |      2 |   100K |00:00:00.21 |    100K |          |
|   6 |    TABLE ACCESS BY INDEX ROWID         | T_ORD_DET    |   100K |      2 |   100K |00:00:00.24 |    100K |          |
[B 1년 · 소트 머지]
|   0 | SELECT STATEMENT                       |              |      1 |        |   100K |00:00:00.44 |    1792 |          |
|   1 |  SORT ORDER BY                         |              |      1 |   100K |   100K |00:00:00.44 |    1792 | 6420K (0)|
|   2 |   MERGE JOIN                           |              |      1 |   100K |   100K |00:00:00.36 |    1792 |          |
|   3 |    SORT JOIN                           |              |      1 |  50000 |  50000 |00:00:00.05 |     896 | 2310K (0)|
|*  4 |     TABLE ACCESS FULL                  | T_ORD        |      1 |  50000 |  50000 |00:00:00.02 |     896 |          |
|*  5 |    SORT JOIN                           |              |  50000 |   200K |   100K |00:00:00.27 |     896 | 8960K (0)|
|   6 |     TABLE ACCESS FULL                  | T_ORD_DET    |      1 |   200K |   200K |00:00:00.03 |     896 |          |
-------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
  [NL]   4 - filter(("O"."ORD_DT">=:D1 AND "O"."ORD_DT"<:D2))
         5 - access("D"."ORD_SEQ"="O"."ORD_SEQ")
  [머지] 4 - filter(("O"."ORD_DT">=:D1 AND "O"."ORD_DT"<:D2))
         5 - access("D"."ORD_SEQ"="O"."ORD_SEQ")
             filter("D"."ORD_SEQ"="O"."ORD_SEQ")

문제 ​

  1. 세 방식 각각이 어떤 순서로 일을 하고 일량이 어디서 나오는지 측정값과 실행계획의 Starts, Used-Mem으로 비교해 서술하시오. 소트 머지 계획 5번 줄의 Starts 50,000이 무엇을 뜻하는지도 쓰시오.
  2. 상담 화면과 회계 배치에 각각 어느 방식을 쓸지 정하고 SQL을 작성하시오. 이번에 지는 방식이 이기는 조건도 쓰시오.
  3. 고른 방식의 부작용과, 힌트 유효기간 대장에 남길 재검토 조건을 숫자로 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자