동시성 문제를 해결하기 위한 MySQL 잠금 두가지

2013-01-01

InnoDB를 사용하면서 동시성(Concurrency)을 고려하지 않고 개발을 하면 큰 문제가 생길 수 있다. 아래에 게임 속 플레이어 간 골드를 넘기는 간단한 로직을 살펴보자.

위 예제 코드는 한 플레이어의 골드를 다른 플레이어에게 주는 로직을 간략하게 한 것이다. 이 코드가 동시에 하나씩 실행되면 문제가 없지만, 그렇지 않은 경우 버그가 발생한다. 우선 한 플레이어의 골드가 옮기려는 1000 골드 이상인지 확인 후 1000 골드를 차감한다. 그 후 받을 플레이어의 골드를 1000 증가시킨 후 트랜잭션을 커밋한다. 만약 커밋이나 롤백을 하기 전에 다른 프로세스가 플레이어 1번의 골드를 조회하는 경우 차감 전의 원래 골드 값을 돌려받아 버그가 생기게 된다.

이를 방지하기 위해서는 InnoDB의 기본 Isolation Level를 변경 할 수 있겠지만, 전체 쿼리 중 동시성을 고려해야 하는 경우가 많지 않다면 간단한 잠금 쿼리를 이용해서 처리 할 수 있다.

InnoDB의 잠금은 두가지로 나눌 수 있다. 하나는 LOCK IN SHARE MODE와 다른 하나는 FOR UPDATE다. 이 둘의 용도는 약간 다르기 때문에 자신의 용도에 따라 신중히 선택을 해야 한다.

LOCK IN SHARE MODE는 SELECT를 한 후에 트랜잭션이 끝날 때까지 해당 ROW 값이 변경되지 않을 것을 보장한다. 바꿔 말하면 해당 ROW를 UPDATE 하거나 DELETE 하려는 쿼리는 잠김 상태가 되어 트랜잭션이 끝날 때까지 대기하게 된다. 하지만, SELECT는 얼마든지 여러 세션이 동시에 수행하는 것이 가능하다.

기존 SELECT 쿼리문 맨 뒤에 LOCK IN SHARE MODE 문장을 추가하는 것만으로 사용이 가능하지만, 트랜잭션이 끝나기 전까지만 유효하므로 auto_commit을 꺼야 한다.

FOR UPDATE는 SELECT로 가져 온 데이터를 변경을 하려고 할 때 사용한다.

FOR UPDATE를 SELECT를 가져온 이후로 해당 ROW에 대해 다른 세션의 SELECT, UPDATE, DELETE 등의 쿼리가 모두 잠김 상태가 된다. 즉, FOR UPDATE를 한 세션 외에 다른 세션들은 모두 해당 ROW에 접근을 할 수 없게 되고, 모두 대기 상태가 된다. FOR UPDATE도 LOCK IN SHARE MODE처럼 트랜잭션이 끝나는 시점에서 풀린다. 예로 든 플레이어 간 골드 이전과 같은 경우에 알맞는 잠금이다.

웹 프로그래밍에서는 쓰레드 프로그래밍처럼 세밀히 동시성을 고민 할 필요는 없지만, 웹이 기본적으로 다수 사용자가 동시에 같은 데이터에 접근을 하는 환경이므로 이로 인해 주의가 필요하다. 이 외에 이런 문제를 해결하기 위해 트리거나 저장 프로시저 등을 이용 할 수 있지만, 최근 웹서비스 초기에는 MySQL을 단순 key-value 저장소처럼 사용하다 추후 서비스 성장에 맞춰 noSQL을 도입하는 경우가 종종 보이므로, RDBMS의 기능을 너무 적극적으로 활용하지 않는 것이 낫지 않을까 싶다.

11 Comments
별자리물고기
2013-01-09 @ 11:29 오전

아… MySQL에도 잠금 기능이 있었군. ^^

응답
    2013-06-29 @ 8:54 오전

    한동안 정신줄을 놓긴 했나 보군. 이 댓글을 이제야 발견하다니 말이야. :-)

    응답
아키라
2013-11-07 @ 5:41 오후

누가 UPDATE 를 처렇게 통으로 합니까? UPDATE TABLE SET point = point + ? 증가치 이렇게 하지.

응답
    2013-11-11 @ 11:25 오전

    소스 코드에 주석으로 달았듯이 일부러 저렇게 사용을 한 것입니다.

    응답
    2014-02-16 @ 1:11 오전

    교육용으로 한거잖아요;;;

    응답
      2014-02-18 @ 5:22 오후

      오해를 줄이기 위해 예제를 변경 했습니다. 감사합니다.

      응답
양돌이
2015-12-03 @ 8:08 오후

동시성 관련 설명감사합니다.
그럼 실제 적용은 어떻게 하나요??

응답
    2015-12-04 @ 10:27 오전

    동시성 문제가 발생될 것으로 예상되는 모든 읽기 목적의 SELECT 문에는 LOCK IN SHARE MODE를 사용하시고, 쓰기 목적으로 사용하는 SELECT 문에는 FOR UPDATE를 사용하시면 됩니다.

    응답

답글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다.


*