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

상담원 둘이 같은 고객을 고치면 먼저 저장한 쪽의 변경이 사라진다

기초UPDATE한빛유통524 / 700

상황 ​

한빛유통 콜센터의 「고객 정보 수정」 화면은 고객을 열 때 그 고객 한 행을 읽어 띄우고, 「저장」을 누르면 화면의 칸을 전부 그대로 UPDATE한다. 저장은 하루 약 3,000번이다. 3월 12일 상담원 1이 마케팅팀 요청대로 고객 CUS_000417의 등급을 B에서 A로 올렸다. 다음 날 그 고객이 "승급 문자를 받았는데 화면에는 아직 B 등급이에요"라고 전화했다. 에러 로그에는 아무것도 없었다.

테이블과 인덱스 ​

  • M_CUS 고객: 2,000행 · 30블록 · PK_M_CUS (CUS_ID) · CUS_GD(A 1% · B 9% · C 90%) · RGN_ID 지역
  • 화면은 커넥션 풀을 쓴다. 고객을 여는 SELECT와 저장 UPDATE는 서로 다른 요청이고, 저장 요청 안에서 UPDATE 뒤 바로 COMMIT한다.
  • 감사 로그: 2월에 같은 고객을 두 사람이 10분 안에 저장한 경우가 41번 있었다.
  • 이 문제에서는 M_CUS에 칸을 더해도 된다.

원본 SQL ​

sql
-- 「고객 정보 수정」: 고객을 열 때
SELECT CUS_ID, CUS_NM, RGN_ID, GND_TP, BTH_YMD, CUS_GD
  FROM M_CUS
 WHERE CUS_ID = :cus_id;

-- 「저장」: 화면의 칸을 전부 그대로 쓴다
UPDATE M_CUS
   SET CUS_NM = :cus_nm, RGN_ID = :rgn_id, GND_TP = :gnd_tp,
       BTH_YMD = :bth_ymd, CUS_GD = :cus_gd
 WHERE CUS_ID = :cus_id;
COMMIT;

실행계획 학습용 예시 ​

xplan
[시간표 — 3월 12일 · 고객 CUS_000417]
시각        상담원 1 (SID 63) · VIP 승급 처리               상담원 2 (SID 118) · 주소 이전 문의
14:02:10    고객 열기 → RGN_ID 03 · CUS_GD B
14:02:25                                                    고객 열기 → RGN_ID 03 · CUS_GD B
14:03:05    CUS_GD 를 A 로 바꿔 저장 → UPDATE 1행 · COMMIT
14:03:46                                                    RGN_ID 를 05 로 바꿔 저장 → UPDATE 1행 · COMMIT
                                                            (화면에 남아 있던 CUS_GD 'B' 까지 그대로 쓴다)
14:04 이후  M_CUS : RGN_ID 05 · CUS_GD B   ← 상담원 1 의 승급이 사라졌다

[저장 UPDATE — ALLSTATS LAST (14:03:46 · 상담원 2)]
-------------------------------------------------------------------------------------------
| Id  | Operation           | Name     | Starts | E-Rows | A-Rows |    A-Time   | Buffers |
-------------------------------------------------------------------------------------------
|   0 | UPDATE STATEMENT    |          |      1 |        |      0 | 00:00:00.01 |       4 |
|   1 |  UPDATE             | M_CUS    |      1 |        |      0 | 00:00:00.01 |       4 |
|*  2 |   INDEX UNIQUE SCAN | PK_M_CUS |      1 |      1 |      1 | 00:00:00.01 |       2 |
-------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - access("CUS_ID"=:CUS_ID)

[V$SESSION — 14:03:46]  enq: TX 기다림 없음 · 에러 없음 (상담원 1 은 41초 전에 COMMIT 했다)

[감사 로그 — 2월]  같은 고객을 10분 안에 두 사람이 저장 41번 · 그중 앞사람의 변경이 사라진 경우 38번

문제 ​

  1. 시간표를 보고 상담원 1의 승급이 왜 사라졌는지, 왜 아무도 기다리지 않았고 에러도 나지 않았는지 설명하시오.
  2. 늦게 저장하는 쪽이 먼저 저장된 변경을 모른 채 덮어쓰지 못하도록 고치시오. 고객을 열 때 SELECT … FOR UPDATE로 잠가 두는 방법이 이 화면에 맞지 않는 이유도 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자