Skip to content
튜닝 연습파싱과 바인드 변수
PARSE-023

바인드 변수로 바꾼 고액 주문 집계에 자식 커서가 40개 넘게 붙는다

중급SELECT한빛유통568 / 700

상황 ​

정산 대시보드의 「기간 고액 주문」 위젯과 같은 이름의 API가 한 문장을 같이 쓴다. 기간(하루 ~ 3년)과 하한 금액을 바인드 변수로 넘기고, 하루 4만 번쯤 실행된다. 지난달 리터럴을 바인드 변수로 바꾼 문장이다. 이번 주 AWR에서 이 문장이 「SQL ordered by Version Count」 1위, 「Parse CPU」 3위에 올랐다. 오전 피크에는 cursor: mutex S 대기도 보인다. 박팀장이 물었다. "바인드로 바꿨으면 커서는 하나여야 하는 것 아닌가요?"

테이블과 인덱스 ​

  • T_ORD_BIG: 1,000,000행 · 5,826블록 · ORD_DT 2022-01-01 ~ 2024-12-31(전부 자정) · ORD_AMT
  • 인덱스 IX_ORD_BIG_DT_AMT (ORD_DT, ORD_AMT): 리프 약 3,320블록 · 6월 한 달이 91블록 · 이 문장이 쓰는 컬럼을 모두 담는다
  • 히스토그램은 ORD_ST에만 있다 · statistics_level = TYPICAL · 바인드 피킹 켬

원본 SQL ​

sql
SELECT COUNT(*) AS ORD_CNT, SUM(ORD_AMT) AS AMT
  FROM T_ORD_BIG
 WHERE ORD_DT >= :d1
   AND ORD_DT <  :d2
   AND ORD_AMT >= :amt

실행계획 학습용 예시 ​

xplan
[V$SQLAREA — 15:00]  VERSION_COUNT 43 · LOADS 43 · EXECUTIONS 41,206 · SHARABLE_MEM 2.6MB

[V$SQL — 자식 43개를 계획별로 묶은 것]
 PLAN_HASH    계획                               자식 수  IS_BIND_AWARE   IS_SHAREABLE = Y   실행     BUFFER_GETS/실행
 2207145832   INDEX RANGE SCAN IX_ORD_BIG_DT_AMT      38  Y 37 · N 1                     6   38,914                 61
 3519962670   INDEX FAST FULL SCAN (같은 인덱스)        5  Y 5                             2    2,292              3,341

[V$SQL_SHARED_CURSOR — Y 칸 세기]  BIND_EQUIV_FAILURE 41 · LOAD_OPTIMIZER_STATS 1
[V$SQL_CS_HISTOGRAM — 자식 0]  0:6  1:3  2:0

[V$SQL_CS_SELECTIVITY — 자식 둘만]
 CHILD  PREDICATE  RANGE_ID  LOW       HIGH
    17  >=D1              0  0.175934  0.215030
    17  <D2               0  0.749863  0.916499
    17  >=AMT             0  0.081052  0.099063
    29  >=D1              0  0.623288  0.761796
    29  <D2               0  0.893151  1.091629
    29  >=AMT             0  0.405000  0.495000

[계획 2207145832 — 한 달 · 하한 10만 원 · 'ALLSTATS LAST']
------------------------------------------------------------------------------------------------------
| Id  | Operation          | Name              | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |                   |      1 |        |      1 |00:00:00.01 |      91 |
|   1 |  SORT AGGREGATE    |                   |      1 |      1 |      1 |00:00:00.01 |      91 |
|*  2 |   FILTER           |                   |      1 |        |   2465 |00:00:00.01 |      91 |
|*  3 |    INDEX RANGE SCAN| IX_ORD_BIG_DT_AMT |      1 |   2471 |   2465 |00:00:00.01 |      91 |
------------------------------------------------------------------------------------------------------
   2 - filter(:D2>:D1)
   3 - access("ORD_DT">=:D1 AND "ORD_AMT">=:AMT AND "ORD_DT"<:D2)
       filter("ORD_AMT">=:AMT)

[계획 3519962670 — 3년 · 하한 1만 원]
------------------------------------------------------------------------------------------------------
| Id  | Operation             | Name              | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |                   |      1 |        |      1 |00:00:00.09 |    3341 |
|   1 |  SORT AGGREGATE       |                   |      1 |      1 |      1 |00:00:00.09 |    3341 |
|*  2 |   FILTER              |                   |      1 |        |   450K |00:00:00.08 |    3341 |
|*  3 |    INDEX FAST FULL SCAN| IX_ORD_BIG_DT_AMT |      1 |   450K |   450K |00:00:00.06 |    3341 |
------------------------------------------------------------------------------------------------------
   2 - filter(:D2>:D1)
   3 - filter(("ORD_DT">=:D1 AND "ORD_AMT">=:AMT AND "ORD_DT"<:D2))

[같은 3년을 INDEX RANGE SCAN으로 (힌트)]  Buffers 3,322 · 0.08초

문제 ​

  1. 텍스트가 하나인 문장에 자식이 43개 생긴 과정을 V$SQL_CS_HISTOGRAM, V$SQL_CS_SELECTIVITY, V$SQL_SHARED_CURSOR로 설명하시오.
  2. 이 문장에 자식이 여럿 필요한지 판단하고, 자식이 하나로 모이면서 어느 기간이든 맞는 계획을 타도록 고치시오. 고친 방법의 함정도 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자