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

SORT JOIN이 2,000번 돈다며 NL로 바꾼 월 청구서 추출이 읽는 블록이 아홉 배가 됐다

기초SELECT한빛유통187 / 700

상황 ​

「월 청구서 추출」은 매달 1일 새벽, 한 달 치 주문을 고객 순으로 뽑아 청구서 출력 프로그램에 넘긴다. 프로그램은 고객 번호가 바뀔 때마다 새 장을 시작하므로 고객 순서가 꼭 맞아야 한다. 지난달 조민우가 계획의 SORT JOIN 줄 Starts가 2,000인 것을 보고 "고객마다 주문을 다시 정렬하느라 2,000번 돈다"라며 힌트를 USE_MERGE에서 USE_NL로 바꿨다. 이번 달 추출은 읽는 블록이 932에서 8,249로 늘었다. 아래는 2024년 6월로 돌린 지금의 계획과 바꾸기 전의 계획이다.

테이블과 인덱스 ​

  • M_CUS: 2,000행 · 30블록 · PK_M_CUS (CUS_ID) · 고객 번호 순으로 적재되어 있다
  • T_ORD: 100,000행 · 896블록 · PK_T_ORD (ORD_SEQ) · IX_T_ORD_CUS_DT (CUS_ID, ORD_DT) · 한 고객의 주문은 테이블 여기저기에 흩어져 있다
  • 2024년 6월 주문 4,172건 · 그달 주문한 고객 1,752명 · 통계 최신

원본 SQL ​

sql
SELECT /*+ LEADING(c o) USE_NL(o) INDEX(o IX_T_ORD_CUS_DT) */
       c.CUS_ID, c.CUS_NM, o.ORD_SEQ, o.ORD_DT, o.ORD_AMT
  FROM M_CUS c, T_ORD o
 WHERE o.CUS_ID = c.CUS_ID
   AND o.ORD_DT >= :v_from
   AND o.ORD_DT <  :v_to
 ORDER BY c.CUS_ID
-- 바꾸기 전 힌트 : /*+ LEADING(c o) USE_MERGE(o) */

실행계획 학습용 예시 · 머지 조인 모양은 교재 06화 실측과 같다 ​

xplan
[지금 · USE_NL]
-----------------------------------------------------------------------------------------------------------
| Id  | Operation                     | Name            | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
-----------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |                 |      1 |        |   4172 |00:00:00.11 |    8249 |
|   1 |  NESTED LOOPS                 |                 |      1 |   4170 |   4172 |00:00:00.11 |    8249 |
|   2 |   NESTED LOOPS                |                 |      1 |   4170 |   4172 |00:00:00.04 |    4077 |
|   3 |    TABLE ACCESS BY INDEX ROWID| M_CUS           |      1 |   2000 |   2000 |00:00:00.01 |      36 |
|   4 |     INDEX FULL SCAN           | PK_M_CUS        |      1 |   2000 |   2000 |00:00:00.01 |       6 |
|*  5 |    INDEX RANGE SCAN           | IX_T_ORD_CUS_DT |   2000 |      2 |   4172 |00:00:00.02 |    4041 |
|   6 |   TABLE ACCESS BY INDEX ROWID | T_ORD           |   4172 |      2 |   4172 |00:00:00.06 |    4172 |
-----------------------------------------------------------------------------------------------------------
   5 - access("O"."CUS_ID"="C"."CUS_ID" AND "O"."ORD_DT">=:V_FROM AND "O"."ORD_DT"<:V_TO)

[바꾸기 전 · USE_MERGE]
----------------------------------------------------------------------------------------------------------
| Id  | Operation                     | Name     | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Used-Mem |
----------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |          |      1 |        |   4172 |00:00:00.02 |     932 |          |
|   1 |  MERGE JOIN                   |          |      1 |   4170 |   4172 |00:00:00.02 |     932 |          |
|   2 |   TABLE ACCESS BY INDEX ROWID | M_CUS    |      1 |   2000 |   2000 |00:00:00.01 |      36 |          |
|   3 |    INDEX FULL SCAN            | PK_M_CUS |      1 |   2000 |   2000 |00:00:00.01 |       6 |          |
|*  4 |   SORT JOIN                   |          |   2000 |   4170 |   4172 |00:00:00.01 |     896 |  267K (0)|
|*  5 |    TABLE ACCESS FULL          | T_ORD    |      1 |   4170 |   4172 |00:00:00.01 |     896 |          |
----------------------------------------------------------------------------------------------------------
   4 - access("O"."CUS_ID"="C"."CUS_ID")
       filter("O"."CUS_ID"="C"."CUS_ID")
   5 - filter(("O"."ORD_DT">=:V_FROM AND "O"."ORD_DT"<:V_TO))

문제 ​

  1. 바꾸기 전 계획의 4번 줄 SORT JOIN Starts 2,000이 무엇을 센 것인지 설명하고, 조민우의 해석이 왜 틀렸는지 쓰시오.
  2. 지금 계획에서 8,249블록이 어디서 나오는지 줄 번호와 Starts로 설명하시오.
  3. 이 추출에 맞는 조인 방식을 정해 SQL을 고치고, 해시 조인과 비교해 그 방식이 나은 이유를 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자