테마
정산팀의 「고객별 정산 명세 생성」 프로시저는 원래 매달 1일 밤에 전 지역을 한 번에 돌렸다. 이번 달부터 고객센터가 월초 오전에 명세를 빨리 달라고 해서, 조민우와 동료 한 명이 화면에서 지역 묶음을 나눠(1 ~ 4, 5 ~ 8) 동시에 돌리기로 했다. 11월 1일 09:00에 두 실행이 모두 '완료'로 끝났는데, 고객센터에서 지역 1 ~ 4 고객의 명세가 하나도 없고 지역 5 ~ 8 고객은 명세가 두 장씩 나온다는 연락이 왔다. 이튿날 다시 돌리자 이번에는 한쪽이 ORA-00054로 실패했다.
T_ORD_STLM 정산: 100,000행 · 640블록 · 10월 정산 전체 · 인덱스 없음M_CUS: 2,000행 · 30블록 · PK_M_CUS (CUS_ID) · RGN_ID 8개 지역. 지역 1 ~ 4 고객 1,012명(정산 50,410건), 지역 5 ~ 8 고객 988명(정산 49,590건)W_STMT(작업 테이블, 이 문제용): 보통의 영구 테이블 · LOGGING · 인덱스 없음 · 명세 머리와 명세 줄 두 단계가 함께 읽는다.T_STMT(명세 머리: 고객별) · T_STMT_DAY(명세 줄: 고객 × 정산일) · 둘 다 이 문제용 테이블이다.W_STMT를 쓰지 않는다.PROCEDURE P_STMT_BUILD(p_ym VARCHAR2, p_rgn_from VARCHAR2, p_rgn_to VARCHAR2) IS
BEGIN
EXECUTE IMMEDIATE 'TRUNCATE TABLE W_STMT'; -- ① 작업 테이블을 비운다
INSERT INTO W_STMT (STLM_SEQ, CUS_ID, RGN_ID, STLM_DT, STLM_AMT) -- ② 이번 지역 묶음의 정산
SELECT s.STLM_SEQ, s.CUS_ID, c.RGN_ID, s.STLM_DT, s.STLM_AMT
FROM T_ORD_STLM s, M_CUS c
WHERE c.CUS_ID = s.CUS_ID
AND c.RGN_ID BETWEEN p_rgn_from AND p_rgn_to;
COMMIT;
INSERT INTO T_STMT (STMT_YM, CUS_ID, RGN_ID, STLM_CNT, STLM_AMT) -- ③ 명세 머리
SELECT p_ym, CUS_ID, RGN_ID, COUNT(*), SUM(STLM_AMT)
FROM W_STMT
GROUP BY CUS_ID, RGN_ID;
INSERT INTO T_STMT_DAY (STMT_YM, CUS_ID, STLM_DT, STLM_CNT, STLM_AMT) -- ④ 명세 줄
SELECT p_ym, CUS_ID, TRUNC(STLM_DT), COUNT(*), SUM(STLM_AMT)
FROM W_STMT
GROUP BY CUS_ID, TRUNC(STLM_DT);
COMMIT;
END;
[11월 1일 — 두 세션의 실행 기록]
시각 세션 단계
09:00:02.1 A (지역 1 ~ 4) ① TRUNCATE W_STMT
09:00:02.6 A ② W_STMT 50,410행 · COMMIT
09:00:03.0 B (지역 5 ~ 8) ① TRUNCATE W_STMT
09:00:03.4 B ② W_STMT 49,590행 · COMMIT
09:00:03.9 A ③ T_STMT 988행 · ④ T_STMT_DAY 21,874행 · COMMIT · 완료
09:00:04.0 B ③ T_STMT 988행 · ④ T_STMT_DAY 21,874행 · COMMIT · 완료
[T_STMT — 10월분 대조]
지역 1 ~ 4 고객 1,012명 명세 머리 0행
지역 5 ~ 8 고객 988명 명세 머리 1,976행 (고객마다 두 행)
[11월 2일 — 다시 돌렸을 때]
09:10:01.2 A ① TRUNCATE · ② INSERT 시작
09:10:01.5 B ① TRUNCATE W_STMT
ORA-00054: resource busy and acquire with NOWAIT specified or timeout expired
[세션 통계 — 실행 한 번 (참고)]
redo size 약 5.1 MB (그중 W_STMT 적재 약 4.6 MB)