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

아무도 고치지 않은 주문인데 '다른 사용자가 먼저 수정했습니다'가 세 번에 한 번 뜬다

중급DDL한빛유통526 / 700

상황 ​

한빛유통 콜센터의 「결제 수단 변경」 화면은 결제 전(PENDING) 주문의 결제 수단을 바꾼다. 지난달 개발팀은 동시 수정으로 변경이 사라지는 일을 막으려고 낙관적 잠금을 넣었다. 테이블은 고치지 않고, 주문을 열 때 ORA_ROWSCN을 함께 읽어 두었다가 저장할 때 비교한다. 반영 다음 날부터 상담원들의 불만이 쏟아졌다. "아무도 안 건드린 주문인데 저장하면 '다른 사용자가 먼저 수정했습니다'가 떠요. 다시 열고 저장하면 또 떠요."

테이블과 인덱스 ​

  • T_ORD 주문: 100,000행 · 896블록(블록 하나에 약 112행) · PK_T_ORD (ORD_SEQ) · 만들 때 옵션을 주지 않았다(기본값 NOROWDEPENDENCIES)
  • 결제 콜백(PG사)과 배송 상태 배치가 최근 주문의 ORD_ST, PAY_DT를 하루 약 46,000번 고치고 바로 COMMIT한다. 최근 주문은 테이블 끝쪽 블록에 모여 있다.
  • 상담원이 주문을 열어 두는 시간은 보통 1 ~ 3분이다.
  • 점검 시간은 매일 02:00 ~ 02:30이다.

원본 SQL ​

sql
-- 주문 열기
SELECT ORD_SEQ, CUS_ID, ORD_ST, PAY_TP, ORD_AMT, ORA_ROWSCN AS ROW_SCN
  FROM T_ORD
 WHERE ORD_SEQ = :ord_seq;

-- 저장 — 연 뒤로 아무도 안 고쳤을 때만 (0행이면 "다른 사용자가 먼저 수정했습니다")
UPDATE T_ORD
   SET PAY_TP = :pay_tp
 WHERE ORD_SEQ = :ord_seq
   AND ORA_ROWSCN = :row_scn;
COMMIT;

실행계획 학습용 예시 ​

xplan
[저장 실패 한 건 — 주문 98,214 (PENDING)]
14:20:03   주문 열기 → ROW_SCN 48,213,907
14:21:40   저장 → UPDATE 0행 → "다른 사용자가 먼저 수정했습니다"
           그 사이 주문 98,214 를 고친 트랜잭션: 없음 (감사 로그)
           같은 블록의 다른 주문을 고친 결제 콜백: 9번 (14:20:11 ~ 14:21:33)

[같은 블록의 행 — 14:21:40]
SELECT ORD_SEQ, ORD_ST, ORA_ROWSCN, DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID) AS BLK
  FROM T_ORD WHERE ORD_SEQ BETWEEN 98201 AND 98312;
ORD_SEQ   ORD_ST     ORA_ROWSCN    BLK
98,201    COMPLETE   48,214,566    1183
98,202    COMPLETE   48,214,566    1183
 …
98,214    PENDING    48,214,566    1183     ← 14:20 이후 아무도 고치지 않았다
 …
98,312    COMPLETE   48,214,566    1183     (112행 모두 같은 값)

[저장 UPDATE — ALLSTATS LAST (0행)]
-----------------------------------------------------------------------------------------------------
| Id  | Operation                     | Name     | Starts | E-Rows | A-Rows |    A-Time   | Buffers |
-----------------------------------------------------------------------------------------------------
|   0 | UPDATE STATEMENT              |          |      1 |        |      0 | 00:00:00.01 |       3 |
|   1 |  UPDATE                       | T_ORD    |      1 |        |      0 | 00:00:00.01 |       3 |
|*  2 |   TABLE ACCESS BY INDEX ROWID | T_ORD    |      1 |      1 |      0 | 00:00:00.01 |       3 |
|*  3 |    INDEX UNIQUE SCAN          | PK_T_ORD |      1 |      1 |      1 | 00:00:00.01 |       2 |
-----------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - filter("ORA_ROWSCN"=:ROW_SCN)
   3 - access("ORD_SEQ"=:ORD_SEQ)

[반영 뒤 일주일]  저장 1,920번 · 0행 실패 604번 (31%) · 실제로 다른 사람이 같은 주문을 고친 경우 3번

문제 ​

  1. 주문 98,214는 아무도 고치지 않았는데 왜 0행이 되는지 ORA_ROWSCN의 성질과 위의 조회 결과로 설명하시오.
  2. 거짓 충돌이 나지 않도록 고치시오. 고친 방법의 대가와, 다른 방법을 골랐을 때의 차이도 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자