dev/database

MariaDB 트랜잭션 사용시 고려사항

재삐신생 2025. 12. 26. 19:42
반응형

트랜잭션 설계시, 주의, 고려 해봄직한 내용

 

 

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 없이 트랜잭션만 걸면 레이스가 그대로 남음

 

반응형