Skip to content
튜닝 연습Lock과 동시성 제어
LOCK-038

정산 배치에 SERIALIZABLE을 넣은 뒤로 아침마다 ORA-08177로 실패한다

중급PLSQL한빛유통533 / 700

상황 ​

한빛유통의 「일 정산」 배치는 매일 09:30에 어제 결제 완료된 주문을 정산 테이블에 적재하고 주문에 정산 표시를 한다. 지난달 대사에서 정산에 들어간 주문과 정산 표시가 된 주문이 3건 달랐다. 개발자는 "두 문장이 같은 시점을 보게 하면 된다"며 맨 앞에 SET TRANSACTION ISOLATION LEVEL SERIALIZABLE을 넣었다. 그 뒤로 4월 평일 20일 가운데 13일 동안 배치가 ORA-08177로 실패해, 오과장이 손으로 다시 돌렸다. 다시 돌리면 대개 성공한다.

테이블과 인덱스 ​

  • T_ORD 주문: 100,000행 · 896블록 · PK_T_ORD (ORD_SEQ) · 인덱스 IX_T_ORD_PAY_DT (PAY_DT) · 이 문제에서는 정산 표시 STLM_YN('N', 'Y')을 더했다 · 어제 결제 완료 주문은 하루 약 1,100건
  • T_ORD_STLM 정산: 100,000행 · 640블록 · STLM_SEQ는 시퀀스 SQ_STLM_SEQ로 매긴다
  • 고객센터는 09:00부터 어제 주문의 취소와 반품을 처리한다(ORD_ST를 CANCEL로 바꾸고 바로 COMMIT, 09:30 ~ 09:31 사이 평균 3건). 늦게 도착한 결제 콜백도 어제 주문을 COMPLETE로 바꾼다.
  • 이미 정산된 주문이 취소되면 다음 날 「정산 취소」 배치가 따로 처리한다.

원본 SQL ​

sql
PROCEDURE DAILY_STLM(p_dt DATE) IS
BEGIN
  SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;    -- 지난달 건수 불일치 뒤 추가

  INSERT INTO T_ORD_STLM (STLM_SEQ, ORD_SEQ, CUS_ID, STLM_DT, STLM_AMT)
  SELECT SQ_STLM_SEQ.NEXTVAL, ORD_SEQ, CUS_ID, p_dt, PAY_AMT
    FROM T_ORD
   WHERE PAY_DT >= p_dt AND PAY_DT < p_dt + 1
     AND ORD_ST = 'COMPLETE' AND STLM_YN = 'N';

  UPDATE T_ORD
     SET STLM_YN = 'Y'
   WHERE PAY_DT >= p_dt AND PAY_DT < p_dt + 1
     AND ORD_ST = 'COMPLETE' AND STLM_YN = 'N';

  COMMIT;
END;

실행계획 학습용 예시 ​

xplan
[4월 17일 — 시간표]
09:30:00.02   배치 SET TRANSACTION ISOLATION LEVEL SERIALIZABLE (읽기 시점 고정)
09:30:00.10   INSERT … SELECT → 1,104행 (주문 97,833 은 COMPLETE 로 보여 적재)
09:30:00.61                                고객센터 주문 97,833 취소 UPDATE ORD_ST = 'CANCEL' · COMMIT
09:30:01.22   UPDATE T_ORD … → ORA-08177: can't serialize access for this transaction
              → 배치 ROLLBACK · 09:52 오과장이 다시 돌려 성공

[INSERT … SELECT — ALLSTATS LAST]
---------------------------------------------------------------------------------------------------------------------
| Id  | Operation                              | Name            | Starts | E-Rows | A-Rows |    A-Time   | Buffers |
---------------------------------------------------------------------------------------------------------------------
|   0 | INSERT STATEMENT                       |                 |      1 |        |      0 | 00:00:00.08 |    1290 |
|   1 |  LOAD TABLE CONVENTIONAL               | T_ORD_STLM      |      1 |        |      0 | 00:00:00.08 |    1290 |
|   2 |   SEQUENCE                             | SQ_STLM_SEQ     |      1 |        |   1104 | 00:00:00.02 |     118 |
|*  3 |    TABLE ACCESS BY INDEX ROWID BATCHED | T_ORD           |      1 |   1080 |   1104 | 00:00:00.01 |     110 |
|*  4 |     INDEX RANGE SCAN                   | IX_T_ORD_PAY_DT |      1 |   1100 |   1180 | 00:00:00.01 |       5 |
---------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   3 - filter(("ORD_ST"='COMPLETE' AND "STLM_YN"='N'))
   4 - access("PAY_DT">=:B1 AND "PAY_DT"<:B1+1)

[지난달 — SERIALIZABLE 을 넣기 전의 불일치 3건]
주문      INSERT … SELECT 때   UPDATE 때      결과
96,410    COMPLETE (적재)      CANCEL         정산에 들어갔는데 STLM_YN 'N' 으로 남음 (취소된 주문이 정산됐다)
96,522    PENDING (빠짐)       COMPLETE       정산에 없는데 STLM_YN 'Y' → 영영 정산되지 않음
96,587    PENDING (빠짐)       COMPLETE       (같은 모양)

[4월 · 평일 20일]  실패 13일 · 다시 돌린 횟수 17번 · 실패 때마다 걸린 행은 배치가 시작한 뒤 고객센터나 결제 콜백이 고친 어제 주문

문제 ​

  1. 지난달의 불일치 3건과 4월의 ORA-08177이 각각 어떻게 생겼는지 격리 수준의 차이로 설명하시오.
  2. 기다리기는 하되 실패하지 않고, 적재와 정산 표시가 늘 같은 주문을 가리키도록 배치를 고치시오. "ORA-08177이 나면 자동으로 다시 돌리자"는 의견에도 답하시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자