테마
한빛유통 고객센터 화면 오른쪽의 「취소 급증」 칸은 최근 30일 취소 주문의 건수와 금액을 보여 준다. 상담 화면을 열 때마다 갱신되어 하루 약 2만 번 호출된다. 같은 프로시저 PKG_STLM_STAT.status_sum을 정산 대시보드의 「미결」 칸과 「완료」 칸(하루 3,000번씩), 02:00 일 마감 배치(완료 한 번)도 부른다. 2021년에 강도현이 상태값마다 리터럴 문장을 따로 두어 만든 프로시저다.
지난주 개발팀이 정적 분석 도구의 「같은 문자열 리터럴 반복」 경고를 없애려고 상태값을 패키지 상수(PKG_CONST)로 모았다. 배포 다음 날부터 「취소 급증」 칸만 한 박자 늦게 뜨고, 이 프로시저의 SQL이 Top SQL에 올랐다. 개발자는 이렇게 말했다. "분기는 그대로 두고 리터럴을 상수로 바꾸기만 했어요. SQL 코딩 가이드 2조에도 '상수로 쓴다'고 되어 있고요."
T_ORD_BIG: 1,000,000행 · 5,826블록 · ORD_DT DATE(전부 자정) 하루 약 913건 · ORD_ST VARCHAR2(10) COMPLETE 90%, PENDING 8%, CANCEL 2% · FREQUENCY 히스토그램 · 행은 ORD_ST, ORD_DT 순서와 상관없이 흩어져 있다IX_ORD_BIG_ST_DT (ORD_ST, ORD_DT)(이 문제에만 있다): 높이 3 · 리프 약 3,000블록no_invalidate => FALSE). 01:00 뒤 이 프로시저를 처음 부르는 것은 02:00 일 마감 배치다.CURSOR_SHARING = EXACT · 바인드 피킹 켬 · PLSQL_OPTIMIZE_LEVEL = 2 · 화면 WAS의 커넥션 풀 30개-- PKG_CONST (지난주 신설) : 상태값 상수
CREATE OR REPLACE PACKAGE PKG_CONST IS
c_st_complete CONSTANT VARCHAR2(10) := 'COMPLETE';
c_st_pending CONSTANT VARCHAR2(10) := 'PENDING';
c_st_cancel CONSTANT VARCHAR2(10) := 'CANCEL';
END PKG_CONST;
-- PKG_STLM_STAT.status_sum (2021) : 지난주 분기 안의 리터럴을 상수로 바꿨다
PROCEDURE status_sum(p_st IN VARCHAR2, o_cnt OUT NUMBER, o_amt OUT NUMBER) IS
BEGIN
-- 상태값마다 문장을 따로 둔다. 값마다 맞는 계획이 다르다. (2021 hd.kang)
CASE p_st
WHEN PKG_CONST.c_st_cancel THEN
SELECT COUNT(*), NVL(SUM(ORD_AMT), 0) INTO o_cnt, o_amt
FROM T_ORD_BIG
WHERE ORD_DT >= TRUNC(SYSDATE) - 30
AND ORD_ST = PKG_CONST.c_st_cancel;
WHEN PKG_CONST.c_st_pending THEN
SELECT COUNT(*), NVL(SUM(ORD_AMT), 0) INTO o_cnt, o_amt
FROM T_ORD_BIG
WHERE ORD_DT >= TRUNC(SYSDATE) - 30
AND ORD_ST = PKG_CONST.c_st_pending;
WHEN PKG_CONST.c_st_complete THEN
SELECT COUNT(*), NVL(SUM(ORD_AMT), 0) INTO o_cnt, o_amt
FROM T_ORD_BIG
WHERE ORD_DT >= TRUNC(SYSDATE) - 30
AND ORD_ST = PKG_CONST.c_st_complete;
END CASE;
END;
[배포 전 · V$SQL · 지난주 화요일 · 상태값마다 문장 하나]
SQL_TEXT (줄임) EXECUTIONS PLAN_HASH 계획 BUFFER_GETS/EXEC
... AND ORD_ST = 'CANCEL' 20,000 1520374851 INDEX RANGE SCAN (IX_ORD_BIG_ST_DT) 552
... AND ORD_ST = 'PENDING' 3,000 3876610259 TABLE ACCESS FULL 5,830
... AND ORD_ST = 'COMPLETE' 3,001 3876610259 TABLE ACCESS FULL 5,830
[배포 후 · V$SQL · 이번 주 화요일 · 문장 하나에 자식 하나]
SQL_TEXT : SELECT COUNT(*), NVL(SUM(ORD_AMT), 0) FROM T_ORD_BIG WHERE ORD_DT >= TRUNC(SYSDATE) - 30 AND ORD_ST = :B1
CHILD 0 · FIRST_LOAD_TIME 02:00:03 (일 마감 배치) · EXECUTIONS 26,001 · PARSE_CALLS 91 · PLAN_HASH 3876610259
IS_BIND_SENSITIVE Y · IS_BIND_AWARE N · BUFFER_GETS/EXEC 5,830
[자식 0 · 「취소 급증」 칸 한 번 · FORMAT 'ALLSTATS LAST +PEEKED_BINDS']
Plan hash value: 3876610259
------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.09 | 5830 |
| 1 | SORT AGGREGATE | | 1 | 1 | 1 |00:00:00.09 | 5830 |
|* 2 | TABLE ACCESS FULL | T_ORD_BIG | 1 | 24651 | 548 |00:00:00.09 | 5830 |
------------------------------------------------------------------------------------
Peeked Binds (identified by name):
----------------------------------
1 - :B1 (VARCHAR2(32), CSID=873): 'COMPLETE'
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter(("ORD_ST"=:B1 AND "ORD_DT">=TRUNC(SYSDATE@!)-30))