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

작년 가을 응급으로 켠 CURSOR_SHARING=FORCE를 되돌리자는 안건이 올라왔다

실전SELECT유통561 / 700

상황 ​

작년 가을 영수증 화면 때문에 공유 풀 경보가 울리자, DBA가 유통 운영 DB 인스턴스에 CURSOR_SHARING = FORCE를 켰다. 영수증 화면은 그 뒤 바인드 변수로 고쳤다. 이번 달 함수 기반 인덱스 사고까지 겪고 나서 박팀장이 안건을 올렸다. "FORCE를 EXACT로 되돌립시다. 다만 끄면 어디가 터지는지 먼저 보고해 주세요." 선배가 V$SQLAREA에서 FORCE가 손댄 문장(텍스트에 SYS_B_가 들어간 문장)을 실행 수로 줄 세웠다. 매출 리포트 담당자는 "월말 리포트만 유난히 느리다"는 말을 덧붙였다.

테이블과 인덱스 ​

  • CF10 판매: 500,000행 · 4,583블록 · CF10_PK (SLE_NO) · IX_CF10_SLE_DT (SLE_DT)(리프 1,393블록) · 하루 약 700건, 한 달 약 20,800건
  • CF11 판매상품: 1,096,708행 · 8,204블록 · CF11_PK (SLE_NO, ITM_NO) · 판매 한 건에 상품 약 2.2개
  • CF01 상품: 109행 · 5블록 · CF12 발주: 734행 · 10블록
  • CURSOR_SHARING = FORCE(인스턴스) · 바인드 피킹 켬 · 매출 리포트는 일 마감(하루 한 번)과 월말(한 달에 한 번)이 같은 프로그램에서 날짜 범위만 바꿔 나간다.

원본 SQL ​

sql
-- ① 판매상품 상세 팝업 (2011년 Java · 오전에만 약 2만 번) : 영수증 번호와 상품 번호를 이어 붙인다
SELECT d.ITM_NO, i.ITM_NM, d.SLE_QTY, d.SLE_UPRC
  FROM CF11 d, CF01 i
 WHERE d.SLE_NO = '20231105-0412'
   AND d.ITM_NO = 1052
   AND i.ITM_NO = d.ITM_NO;

-- ② 매출 리포트 (일 마감과 월말이 같은 문장 모양) : 날짜 범위를 리터럴로 쓴다
SELECT h.SHP_NO, SUM(d.SLE_QTY * d.SLE_UPRC) AS SLE_AMT
  FROM CF10 h, CF11 d
 WHERE h.SLE_DT BETWEEN '20230601' AND '20230630'
   AND d.SLE_NO = h.SLE_NO
 GROUP BY h.SHP_NO;

-- ③ 영수증 조회 (작년에 바인드 변수로 고쳤다)
SELECT h.SLE_NO, h.SLE_DT, h.SLE_HMS, d.ITM_NO, d.SLE_QTY
  FROM CF10 h, CF11 d
 WHERE h.SLE_NO = :1
   AND d.SLE_NO = h.SLE_NO;

-- ④ 발주 진행 탭 (상태값 셋 · 탭마다 상수)
SELECT ORD_NO, ORD_DT, PTNR_NO FROM CF12 WHERE ORD_PRGS_CD = '12' ORDER BY ORD_DT DESC;

-- 안건 : 오늘 밤
ALTER SYSTEM SET CURSOR_SHARING = EXACT SCOPE = BOTH;

실행계획 학습용 예시 ​

xplan
[V$SQLAREA · 오전 09 ~ 12시 · FORCE가 손댄 문장 (SQL_TEXT에 SYS_B_) · 실행 수 순]
  #   EXECUTIONS   VERSION_COUNT   SQL_TEXT (앞부분)
  ①       21,480               1   SELECT d.ITM_NO, i.ITM_NM, d.SLE_QTY, d.SLE_UPRC FROM CF11 d, CF01 i WHERE d.SLE_NO = :"SYS_B_0" AND d.ITM_NO = :"SYS_B_1" …
  ④          412               1   SELECT ORD_NO, ORD_DT, PTNR_NO FROM CF12 WHERE ORD_PRGS_CD = :"SYS_B_0" ORDER BY ORD_DT DESC
  ②            1               1   SELECT h.SHP_NO, SUM(d.SLE_QTY * d.SLE_UPRC) AS SLE_AMT FROM CF10 h, CF11 d WHERE h.SLE_DT BETWEEN :"SYS_B_0" AND :"SYS_B_1" …
  (③은 SYS_B_가 없다 · 같은 시간 실행 18,730)

[② 매출 리포트 · 지난달 커서 하나로 일 마감 30번과 월말 1번을 처리 · 아래는 월말 실행]
Plan hash value: 3318806174   (6월 1일 일 마감 때 하드 파싱 · Peeked Binds '20230601', '20230601')

---------------------------------------------------------------------------------------------------------------
| Id  | Operation                              | Name           | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
---------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                       |                |      1 |        |      6 |00:00:00.61 |   83614 |
|   1 |  HASH GROUP BY                         |                |      1 |      6 |      6 |00:00:00.61 |   83614 |
|   2 |   NESTED LOOPS                         |                |      1 |   1540 |  45760 |00:00:00.55 |   83614 |
|   3 |    NESTED LOOPS                        |                |      1 |   1540 |  45760 |00:00:00.31 |   62781 |
|   4 |     TABLE ACCESS BY INDEX ROWID BATCHED| CF10           |      1 |    706 |  20833 |00:00:00.04 |     251 |
|*  5 |      INDEX RANGE SCAN                  | IX_CF10_SLE_DT |      1 |    706 |  20833 |00:00:00.01 |      60 |
|*  6 |     INDEX RANGE SCAN                   | CF11_PK        |  20833 |      2 |  45760 |00:00:00.19 |   62530 |
|   7 |    TABLE ACCESS BY INDEX ROWID         | CF11           |  45760 |      1 |  45760 |00:00:00.17 |   20833 |
---------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   5 - access("H"."SLE_DT">=:SYS_B_0 AND "H"."SLE_DT"<=:SYS_B_1)
   6 - access("D"."SLE_NO"="H"."SLE_NO")

  (같은 커서로 일 마감 하루치를 돌리면 Buffers 2,836)

[참고 · 월말 문장을 내 세션에서 EXACT로 따로 파싱하면 (ALTER SESSION)]
  HASH JOIN · CF10 인덱스 범위 251 + CF11 FULL 8,204 → Buffers 8,455 · 0.31초

[① 팝업 · 내 세션만 EXACT로 바꿔 2만 번 재현 (ALTER SESSION SET CURSOR_SHARING = EXACT)]
  parse count (hard) 20,014 · parse time cpu 1,640 cs · 경과 19.2초   (FORCE에서는 1.9초)

문제 ​

  1. 오늘 밤 그대로 EXACT로 바꾸면 ①~④가 각각 어떻게 되는지 위 자료로 예측하시오.
  2. 바꾸기 전에 고칠 SQL을 고쳐 쓰고, 그대로 둘 SQL은 왜 그대로 두는지 쓰시오.
  3. 전환 순서와 전환 뒤에 지켜볼 숫자, 되돌릴 조건을 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자