Spring DB : DB 락 - 개념 이해

    이 글은 인프런 김영한님의 강의를 복습하며 작성한 글입니다.

    DataBase Lock의 개념 이해

    두 개의 DB 세션이 존재한다고 가정해보자. 세션 1이 A라는 행의 데이터를 수정중이고 이를 아직 커밋하지 않은 상황이다. 이 때, 세션2가 A라는 행에 접근해서 데이터를 수정하는 상황이 있다고 가정해보자. 한 마디로 설명하면, A라는 곳을 동시에 여러 세션이 수정하려고 하는 상황이다. 이러면 문제가 없을까? 문제가 있다. 데이터의 정합성이 무너진다. 

    가장 큰 이유 중 하나는 트랜잭션의 원자성이 무너진다는 것이다. 위의 그림을 예로 들어보자. 세션1은 A → B → C 순으로 접근해서 각각의 데이터를 수정하는 일을 한다고 가정한다. 세션1이 A의 값을 수정하고, B의 값을 수정하는 순간 세션2가 A의 값을 다시 수정한다고 가정해보자. 그리고 세션1이 C의 값을 수정하는 순간 문제가 발생해서 세션1의 트랜잭션이 롤백된다고 가정해보자.

    그렇다면 이 때, 'A'의 값은 어떤 값으로 롤백 되는 것이 맞을까? 트랜잭션이 시작되기 전 지점으로 돌아가는 것이 맞을까? 아니면 세션2가 수정한 값으로 변경한 값이 맞을까? 아주 많은 경우의 수가 존재하게 될 것이다. 

    이처럼 여러 세션이 동시에 동일한 곳에 접근해서 데이터를 수정하는 경우, 데이터의 정합성이 박살나는 문제가 발생한다. 따라서 한 세션이 데이터를 수정중인 경우 다른 세션은 데이터를 수정하지 못하게 하는 것이 바람직하다. 이 때 사용하는 것이 DB Lock이다.

     

    DB Lock

    DB에서 데이터 수정은 DB Lock을 가진 세션만 가능하다. 데이터를 수정하기 위해 접근하면, 세션은 DB Lock을 얻을 수 있는지 확인하고 가능하면 바로 획득한다. 그리고 트랜잭션이 커밋될 때, DB Lock을 반납한다. 이유는 위에서 설명을 했다. 위의 이미지를 바탕으로 상황을 다시 한번 살펴보자. 

    세션1이 A → B → C의 값을 각각 수정한다. 이 때, 세션1이 트랜잭션을 수행하고 있다면 세션1은 A,B,C에 대한 DB Lock을 획득한다. 이 때, 세션2가 A에 접근해서 값을 수정하려고 하는데, A의 DB Lock은 세션1이 가지고 있다. 따라서 세션2는 세션1이 DB Lock을 반환하는 순간까지 기다린다.

    세션1이 트랜잭션을 수행하고, 커밋을 했다. 이 때, 세션1은 가지고 있던 DB Lock을 반환한다. 세션2는 DB Lock을 얻, 이제서야 A에 접근해서 값을 수정할 수 있게 된다. 

     

    DB Lock 실습

    H2 DB에서 세션1 / 세션2를 바탕으로 DB Lock의 동작을 확인해본다. 실습에 필요한 쿼리문은 다음과 같다.

    // 기본 데이터 셋팅
    set autocommit false;
    insert into member(member_id, money) values ('memberA', 10000);
    commit;
    
    // 세션1 업데이트 문
    set autocommit false;
    update member set money = 500 where member_id = "memberA";
    
    
    // 세션2 업데이트 문
    set autocommit false;
    update member set money = 5555 where member_id = "memberA";

    위의 쿼리문을 바탕으로 실습을 시작해본다.

    먼저 기본 데이터가 잘 설정되었는지를 확인한다. 세션1 / 세션2에 모두 데이터가 정상적으로 셋팅된 것을 확인할 수 있다. 

    이 때, 세션1은 위의 업데이트문을 실행한다. 커밋은 하지 않았고, 쿼리가 둘다 정상적으로 실행되서 DB에 임시 상태로 반영된 것을 확인할 수 있다. 여길 좀 더 자세히 살펴보면, 세션1은 'memberA' Row의 DB Lock을 획득할 수 있는 상황이었다. 따라서 'memberA'의 DB Lock을 획득하면서, 'memberA' Row에 쿼리를 보낼 수 있게 된 것이다.

    DB에서 확인할 경우, 세션1은 500으로 수정된 값이 보이고 세션2는 기존 값인 10,000을 확인할 수 있다. 아직까지 세션1은 커밋을 하지 않았다. 커밋을 하지 않았기 때문에 세션1은 아직까지 memberA의 DB Lock을 가지고 있는 상황이다.

    // 세션2 업데이트 문
    set autocommit false;
    update member set money = 5555 where member_id = 'memberA';

    이 때, 세션2에서 다음 업데이트 문을 실행해보자.

    실행 시, 실제 실행된 쿼리는 setauttocommit 만 있는 것을 볼 수 있다. 뒤에 있는 update 쿼리문은 실행이 되지 않은 상황이다. 왜 그럴까? 왜냐하면 세션2가 접근하고자 하는 'memberA'의 DB Lock을 세션2가 획득할 수 있는 상황이 아니기 때문이다. 현재 세션1은 트랜잭션을 열었고, 'memberA'의 DB Lock을 가진 상황에서 Commit을 하지 않았기 때문에 계속 DB Lock을 가지고 있다. 따라서 세션2는 MemberA가 DB Lock을 반환할 때 까지 기다렸다가 DB Lcok을 얻을 수 있는 시점에서 처리를 한다.

    그렇지만 DB Lock을 얻기 위해 무작정 대기하지는 않는다. 그렇게 할 경우, DB가 마르기 때문에 적절한 순간까지만 기다린다. 기다려도 DB Lock을 얻지 못하면 해당 세션은 Time out 에러를 내고 트랜잭션을 종료해준다.

    만약 세션2가 TimeOut이 나기 전에 세션1이 Commit을 처리해주면, Commit 처리되는 순간 세션1은 DB락을 반납한다. 따라서 Commit 되는 순간 세션2는 DB Lock을 얻고 필요한 쿼리를 바로 처리해준다.

    이 때 각 세션의 데이터를 확인해보면 다음과 같다. 세션1에게 MemberA는 커밋된 상태로 '500'의 값을 가지고 있는 것이 확인된다. 그리고 세션1은 어떠한 DB Lock도 가지고 있지 않다. 반면 세션2는 이제 DB Lock을 획득한 상태로 업데이트쿼리를 보내서 5555라는 값을 MemberA에서 가지고 있는 것을 볼 수 있다. 

     

    타임아웃 설정

    // 5초까지만 기다림.
    set lock_timeout 50000;

    앞서 이야기했던 것처럼 각 세션은 DB Lock을 획득하기 위해 무한정 대기하지 않는다. 특정 시간만큼만 대기한 후, 그 시간까지 DB Lock을 얻지 못하면 타임 아웃 에러를 발생한다. 타임아웃 시간을 직접 설정하고 싶을 때가 있을텐데, 위의 명령어를 사용하면 설정할 수 있다.

     

    정리

    1. 동시에 여러 세션에서 동일한 곳의 값을 수정할 경우 트랜잭션의 원자성이 무너진다. 따라서, DB는 기본적으로 동일한 곳에 데이터를 수정할 수 없도록 한다.
    2. DB Lock이라는 개념을 도입해서 동일한 곳에 여러 세션이 데이터를 동시에 수정하는 것을 방지한다. DB Lock을 가진 세션만 해당 위치의 값을 수정할 수 있게 된다.
    3. DB Lock을 얻지 못하는 세션은 DB Lock을 얻을 때까지 기다렸다가, DB Lock을 얻으면 데이터를 처리한다. 이 때, DB Lock을 얻기 위해 무한정 대기하지 않고 어느정도의 시간이 지나면 TimeOut 에러가 발생하고 종료된다.
    4. DB Lock은 트랜잭션을 커밋하는 순간 반납된다.

    댓글

    Designed by JB FACTORY