Post

(Transaction) lost update problem (isolation level, deadlock, update lock)

포인트 읽기 - 포인트 차감 순으로 Mysql DB 작업이 발생하는 상황이었다.

1
2
3
4
5
6
7
8
@Transactional
public foo bar() {
    // SELECT
    MemberRestMileageInfo member = getMemberMileageByKey(memberKey);
    ...
    // UPDATE
    memberInfoMapper.update(member);
}

이런 상황에서 같은 memberKey 를 대상으로 빠르게 두 번 요청하면 갱신 손실 문제가 발생한다.

1
2
3
4
5
6
7
        T1                                                T2
start transaction                     
                                                start transaction
SELECT<point : 10000>                                          
                                                SELECT<point : 10000>
UPDATE<point : 5000>
                                                UPDATE<point : 9000>

즉, 먼저 들어온 트랜잭션이 UPDATE 하기 전에 다른 트랜잭션이 SELECT를 해버리면, 먼저 들어온 트랜잭션 T1의 UPDATE는 덮어써져서 사라져버린다.
그래서 두 트랜잭션이 동시에 실행되지 않도록 하기 위해, 처음에는 고립 수준(isolation level)을 SERIALIZABLE로 조정했다. 그러나 이는 deadlock을 유발했다.

트랜잭션 고립 수준을 SERIALIZABLE 로 설정했을 때 발생하는 deadlock 문제

SERIALIZABLE은 SELECT로 가져온 row에 대해 read lock(shared lock)을 걸어준다.
read lock만 가지고 있기 때문에, 이후 UPDATE를 만나면 write lock을 획득하려 시도한다.

[!info] read lock 걸려있는 row에 대해서, 타 스레드에서 read는 가능하고, write는 lock이 풀릴 때 까지 불가. pending하게 된다.

1
2
3
4
5
6
7
        T1                                        T2
start transaction                     
                                        start transaction
SELECT<read_lock_for_member1-1 획득>                                          
                                        SELECT<read_lock_for_member1-2 획득>
UPDATE<write_L1 획득하기 위해 read_lock_for_member1-2 해제 대기>
                                        UPDATE<wirte_L2 획득 위해 read_lock_for_member1-1 해제 대기. deadlock 발생>
  • T1, T2가 모두 member1에 대한 shared lock을 가지고 있는 상태에서, exclusive lock을 획득하려고 시도하니 데드락에 걸린다.
  • http://www.gurubee.net/lecture/2396 - 2.가. Lock 종류 부분에 이러한 문제가 잘 나와 있다.

sol

SELECT FOR UPDATE

  • 위와 같은 문제를 해결하기 위해서는 고립 수준은 기본으로 두고, 처음에 SELECT 할 때 부터 read lock(shared lock) 대신 write lock(update lock)을 획득하면 된다.
  • write lock은exclusive lock이므로, 어떤 트랜잭션이 실행 중이라면 다른 트랜잭션은 기존 트랜잭션이 lock을 release할 때 까지 pending 상태가 된다.
  • MySQL에서 update lock을 획득하기. FOR UPDATE

참고

[!tip] PROPAGATIONISOLATION과는 다르다!
PROPAGATION: 트랜잭션 내에서 또 다른 트랜잭션을 만나는 경우 어떻게 처리할 것인지를 결정한다.
ISOLATION: 트랜잭션에서 접근하는 데이터의 고립 수준을 결정한다. (e.g., READ_COMMITTED)

Deadlock 데드락

This post is licensed under CC BY 4.0 by the author.