반응형
트랜잭션 설계시, 주의, 고려 해봄직한 내용
Java기준, 멀티쓰레드 + 커넥션 풀 환경이라면, 단일 서버 + 단일 DB 환경일지라도, 동시성 제어가 필요한 환경이다.
데이터 유실, 업데이트 누락, Read -> Write 불일치 등등
반드시, 트랜잭션 + 잠금이 필요하다.
START TRANSACTION을 해주더라도, 원자성을 보장해주다는 개념이지, 동시성 제어와는 무관하다.
위험한 방법 1
START TRANSACTION;
SELECT gold FROM player WHERE account_id=?; -- 그냥 읽기
-- 애플리케이션에서 계산
UPDATE player SET gold=? WHERE account_id=?;
COMMIT;
전형적인 “SELECT→비즈니스 계산→UPDATE”
- 다른 트랜잭션이 중간에 UPDATE를 해버릴 수 있음
- 내 트랜잭션은 옛날 값을 기반으로 UPDATE
- 결과: lost update / 중복 지급 / 잔액 음수 / 상태 꼬임
해결책:
- FOR UPDATE 사용
위험한 방법 2
“존재 확인 후 INSERT” (중복 삽입/경쟁)
중복 PK 상황
해결책:
- UNIQUE KEY 걸고 INSERT를 시도해서 실패 처리
- 또는 SELECT ... FOR UPDATE + 적절한 인덱스/락 설계(하지만 InnoDB에서 갭락/격리수준 이슈가 얽힘)
안전한 방법 1
START TRANSACTION;
SELECT value FROM table WHERE id = 1 FOR UPDATE;
UPDATE table SET value = value - 50 WHERE id = 1;
COMMIT;
FOR UPDATE 사용하여, Lock을 걸어준다.
안전한 방법 2
START TRANSACTION;
UPDATE player SET gold = gold - 50 WHERE account_id = ? AND gold >= 50;
COMMIT;
원자적 UPDATE로 처리 ( 이 경우엔 사실 START TRANSACTION도 크게 의미가 없음 )
Affected Row 로 처리여부 확인 필요
그외 추가 고려사항
트랜잭션 격리수준으로 커버 가능? (REPEATABLE READ면 되나?)
MariaDB/InnoDB 기본이 대개 REPEATABLE READ인데,
- REPEATABLE READ는 내가 본 “스냅샷”을 유지해줄 뿐이지
- 다른 트랜잭션의 UPDATE를 막지 않습니다.
- 즉, “내가 읽은 행이 바뀌지 않게 강제로 고정”하려면 결국 락이 필요합니다.
읽은 값 기반으로 업데이트해야 한다면:
- SELECT ... FOR UPDATE (또는 LOCK IN SHARE MODE/FOR SHARE 계열)
- 혹은 원자적 UPDATE로 설계를 바꾸는 게 정석
DB 무결성 유지를 위한 실무 결론 (게임 서버 관점)
- 해야할것
- 가능하면 SELECT→UPDATE 패턴을 없애고, 조건 포함 UPDATE 1방으로 만들기
- 여러 테이블을 함께 갱신/로그 남기면 START TRANSACTION으로 묶기
- 하지말것
- “읽은 값이 전제”인 로직(잔액 체크, 수량 체크, 상태 전이)은 FOR UPDATE 없이 트랜잭션만 걸면 레이스가 그대로 남음
반응형