Spring DB : DB 예제4 - 트랜잭션과 계좌이체

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

    트랜잭션과 계좌이체

    앞서서 트랜잭션을 설명하며 계좌이체에 대한 이야기를 했었다. 먼저 그 내용을 간단히 한번 상기시켜본다. memberA에서 memberB에게 5000원을 이체하는 상황을 가정한다.

    1. memberA의 잔고에서 5000원을 뺀다.
    2. memberB의 잔고에서 5000원을 증가시킨다. 

    계좌이체를 하기 위해서는 위 두 가지 일이 진행되어야 한다고 했다. 그리고 모두 성공되어야 하나의 일로써 인정을 받는다. 하나라도 실패한다면 이 일은 실패한 일이 되고 이 일이 시작하기 전 처음으로 돌아가야한다.

     

    트랜잭션 실습

    1. 계좌이체 정상 성공
    2. 계좌이체 문제 상황 발생 + 커밋
    3. 계좌이체 문제 상황 발생 + 롤백 

    이 트랜잭션 실습에서는 계좌이체와 관련된 세 가지 상황을 유발하고 확인할 것이다. 먼저 실습에 사용할 기본적인 SQL 코드는 다음과 같다.

    // 기본 데이터 입력
    set autocommit false;
    insert into member(member_id, money) values ('memberA', 10000);
    insert into member(member_id, money) values ('memberB', 10000);
    commit;

    위는 기본 데이터를 입력하는 SQL 쿼리문이다. 잔고가 1만인 memberA / memberB 회원을 넣어준다.

    // 계좌이체 쿼리문
    set autocommit false;
    update member set money = 10000 - 5000 where member_id = 'memberA'
    update member set money = 10000 + 5000 where member_id = 'memberB'
    commit;

    memberA에서 5000원을 빼고 memberB로 5000원을 이동시키는 쿼리를 작성한다. 

     

    계좌이체 정상 상황

    좌 : 세션1 / 우 : 세션2

    먼저 초기 데이터를 입력해둔다. 그리고 각 세션에서 값이 정상적으로 확인되는지를 체크한다.

    set autocommit false;
    update member set money = 10000 - 5000 where member_id = 'memberA';
    update member set money = 10000 + 5000 where member_id = 'memberB';

    세션1에서 다음 쿼리문을 실행해준다. 그리고 아직 커밋은 하지 않는다.

    쿼리문을 실행 후 세션1 / 세션2에서 조회되는 데이터를 살펴본다. 세션1은 계좌이체가 정상적으로 반영되어있는 것이 보인다. 그렇지만 커밋이 되지 않아 DB에 임시로 저장된 상태기 때문에, 세션2에서는 값이 조회되지 않는다. 

    commit;

    세션1에서 커밋을 한 후 값을 조회해본다.

    세션1에서 커밋을 했기 때문에 임시 상태로 DB에 저장되어있던 값이 실제 DB에 반영되었다. 따라서 세션2에서도 변경점을 확인할 수 있고, 계좌이체가 정상적으로 이루어져 DB에서도 조회가 가능한 것을 확인할 수 있다. 

     

    계좌이체 문제 상황 + 커밋

    세션1 / 세션2에서 SELECT 문을 이용해 데이터가 정상적으로 저장이 되었는지 확인한다. 둘다 동일하게 10,000원이 셋팅되어있는 것을 볼 수 있다. 

    // 계좌이체 쿼리문
    set autocommit false;
    update member set money = 10000 - 5000 where member_id = 'memberA'
    
    // 문제상황 발생
    update member set money = 10000 + 5000 where member_idddddddd = 'memberB'

    이제 세션1에서 다음 쿼리문을 실행해본다. 두번째 쿼리문은 where에 들어가는 파라미터가 잘못 들어가 SQL 문법 에러가 발생한다.

    실제로 다음과 같이 문제가 발생한 것을 확인할 수 있다.

    이 때 DB 상태를 확인해본다. 세션1은 첫번째 쿼리는 정상 수행, 두번째 쿼리는 오류가 발생했기 때문에 memberA의 계좌 잔고만 5000원이 줄어든 상태다. 아직 커밋이 되지 않았기 때문에 세션2에서는 해당 값을 확인할 수 없다. 이 때, 커밋을 하면 어떻게 될까?

    memberB의 계좌잔고가 올라가야하는데 올라가지 않은 상태로 커밋이 되었다. 그리고 임시 상태였던 DB의 값들은 모두 확정되어서 다른 세션에서도 조회가 될 수 있도록 했다. 실제 memberB의 기대 Money는 15,000원이었는데, 10,000원이 나오게 되면서 DB 정합성에 심각한 문제가 발생하게 되었다.

    이런 이유는 앞서 이야기했던 것처럼 트랜잭션은 '실패했을 때 해당 트랜잭션이 모두 실패해야'한다라는 대정의를 지키지 않았기 때문에 발생한다. 

     

    계좌이체 문제 상황 - 롤백

    먼저 세션1 / 세션2의 초기값을 확인한다. 

    // 계좌이체 쿼리문
    set autocommit false;
    update member set money = 10000 - 5000 where member_id = 'memberA'
    
    // 문제상황 발생
    update member set money = 10000 + 5000 where member_idddddddd = 'memberB'

    다음 잘못된 쿼리문을 실행한다.

    첫번째 쿼리는 정상적으로 실행, 두번째 쿼리는 실패했다. 이 때, DB를 조회하면 세션1은 memberA만 변경된 것이 확인되고, 세션2에서는 커밋 전이기 때문에 초기 값만 볼 수 있다. 왜냐하면 두번째 쿼리가 실패했기 때문에 세션1에서 memberA의 값은 변화가 없는 것이다.

    rollback;

    이 때, 세션1의 작업 중 하나의 작업이 실패했기 때문에 위의 명령어를 이용해 롤백을 실행한다. 

    롤백 실행 후, 세션1 / 세션2에 저장된 데이터를 확인해본다. 세션2의 값은 변함이 없으며, 세션1의 값은 트랜잭션이 시작하기 이전의 초기값으로 돌아갔다. 이것은 RollBack을 했기 때문이다. 롤백은 트랜잭션 내에 있는 쿼리문 중 하나라도 실패하면 반드시 실행시켜서 초기 상태로 돌려주는 것이 맞다. 그것이 트랜잭션을 지키는 방법이다.

     

    정리

    • 이번 SQL 실습을 통해 트랜잭션의 원자성을 확인할 수 있었다.
      • 트랜잭션 내의 작업은 모두 성공하거나 모두 실패해야한다. 실패 시, 트랜잭션 시작 전의 상태로 돌아가야한다.
      • 트랜잭션의 원자성 덕분에 여러 SQL 명령어를 하나의 작업처럼 처리하거나 되돌릴 수 있었다.
    • 오토커밋이라면?
      • 오토커밋은 쿼리 한 줄 실행할 때 마다 커밋이 실행된다.
      • 오토커밋 상태에서는 두번째 쿼리문이 실패했을 때에 롤백을 하면, 두번째 쿼리문만 롤백이 된다. 왜냐하면 첫번째 쿼리문은 이미 실행되고 커밋되어 DB에 반영된 상황이다. 
      • 두 가지 일이 한꺼번에 실패하거나 한꺼번에 성공해야하는데, 오토커밋을 사용할 경우 그렇게 대응을 할 수 없게 된다.
      • 따라서 수동 커밋이 필요하다.
    • 트랜잭션 시작 = 수동커밋 설정
      • 관례적으로 수동커밋 모드를 설정하는 것을 트랜잭션의 시작으로 이야기 한다.
      • 수동커밋 모드를 설정할 경우, 원하는 범위까지 쿼리를 처리하고 커밋을 통해 한번에 일을 실행할 수 있기 때문이다. 

    댓글

    Designed by JB FACTORY