5 분 소요


개념 이해

데이터베이스는 트랜잭션이라는 개념을 지원하는데, 트랜잭션은 데이터베이스에서 하나의 거래를 안전하게 처리하도록 보장해주는 것을 뜻한다.

5000원 계좌이체 예시

  1. A의 잔고를 5000원 감소
  2. B의 잔고를 5000원 증가

만약 둘 중 하나의 작업만 성공한다면 심각한 문제가 발생하기 때문에, 계좌이체는 이렇게 2가지 작업이 합쳐져서 하나의 작업처럼 동작해야 한다.
데이터베이스가 제공하는 트랜잭션 기능을 사용하면 1,2 둘 다 성공해야 저장하고, 중간에 하나라도 실패하면 거래 전의 상태로 돌아갈 수 있다.
모든 작업이 성공해서 데이터베이스에 정상 반영하는 것을 Commit이라 하고, 작업 중 하나라도 실패해서 거래 이전으로 되돌리는 것을 Rollback이라 한다.


트랜잭션 ACID

트랜잭션은 원자성(Atomicity), 일관성(Consistency), 격리성(Isolation), 지속성(Durability)을 보장해야 한다.

  • 원자성: 트랜잭션 내에서 실행한 작업들은 마치 하나의 작업인 것 처럼 모두 성공하거나 모두 실패해야 한다.
  • 일관성: 모든 트랜잭션은 일관성 있는 데이터베이스 상태를 유지해야 한다.
    • Ex) 데이터베이스에서 정한 무결성 제약 조건을 항상 만족해야 한다.
  • 격리성: 동시에 실행되는 트랜잭션들이 서로에게 영향을 미치지 않도록 격리한다.
    • Ex) 동시에 같은 데이터를 수정하지 못하도록 해야 한다.
    • 격리성은 동시성과 관련된 성능 이슈로 인해 트랜잭션 격리 수준(Isolation level)을 선택할 수 있다.
  • 지속성: 트랜잭션을 성공적으로 끝내면 그 결과가 항상 기록되어야 한다. 중간에 시스템에 문제가 발생해도 데이터베이스 로그 등을 사용해서 성공한 트랜잭션 내용을 복구해야 한다.

트랜잭션 격리 수준

Isolation level

  • READ UNCOMMITED(커밋되지 않은 읽기)
  • READ COMMITED(커밋된 읽기)
  • REPEATABLE READ(반복 가능한 읽기)
  • SERIALIZABLE(직렬화 가능)

[참고] : 일반적으로 READ COMMITED(커밋된 읽기) 트랜잭션 격리 수준을 많이 사용한다.


데이터베이스 연결 구조

데이터베이스 연결 구조1

22

  • 사용자는 WAS나 DB 접근 툴 같은 클라이언트를 사용해서 데이터베이스 서버에 접근할 수 있다. 클라이언트는 데이터베이스 서버에 연결을 요청하고 커넥션을 맺게 된다. 이때 데이터베이스 서버는 내부에 세션이라는 것을 만든다. 그리고 앞으로 해당 커넥션을 통한 모든 요청은 이 세션을 통해서 실행하게 된다.
  • 쉽게 풀자면 개발자가 클라이언트를 통해 SQL을 전달하면 현재 커넥션에 연결된 세션이 SQL을 실행한다.
  • 세션은 트랜잭션을 시작하고, 커밋 또는 롤백을 통해 트랜잭션을 종료한다. 그리고 이후에 새로운 트랜잭션을 다시 시작할 수 있다.
  • 사용자가 커넥션을 닫거나, 또는 DB관리자가 세션을 강제로 종료하면 세션은 종료된다.

데이터베이스 연결 구조2

23

  • 커넥션 풀이 10개의 커넥션을 생성하면, 세션도 10개 만들어진다.


트랜잭션 - DB 예제1

개념 이해

트랜잭션 사용법

  • 데이터 변경 쿼리를 실행하고 데이터베이스에 그 결과를 반영하려면 commit을 호출하고, 결과를 반영하고 싶지 않으면 rollback을 호출하면 된다.
  • 커밋을 호출하기 전까지는 임시로 데이터를 저장하는 것이다. 따라서, 해당 트랜잭션을 시작한 세선(사용자)에게만 변경 데이터가 보이고, 다른 세션(사용자)에게는 변경 데이터가 보이지 않는다.
  • 등록, 수정, 삭제 모두 같은 원리로 동작한다.

기본 데이터
24

  • 세션1, 세션2 모두 기본 테이블을 조회하면 해당 데이터가 그대로 조회된다.

세션1 신규 데이터 추가
25

  • 세션1은 트랜잭션을 시작하고 신규 데이터를 DB에 추가했으며 commit은 하지 않았다.
  • 새로운 데이터는 임시 상태로 저장된다.
  • 세션1은 select쿼리를 실행해서 본인이 입력한 신규 데이터를 조회할 수 있다.
  • 세션2는 select쿼리를 실행해도 신규 데이터를 확인할 수 없다.(세션1이 commit 하지 않았기 때문)

커밋하지 않은 데이터를 다른 곳에서 조회할 수 있다면?
세션2에서 세션1이 아직 커밋하지 않은 변경 데이터가 보인다면, 세션1이 롤백 했을 때 심각한 문제가 발생할 수 있기 때문에, 커밋 전의 데이터는 다른 세션에서 보이지 않는다.

세션1 신규 데이터 추가 후 commit
26

  • 세션1이 신규 데이터를 추가한 후에 commit을 호출했다.
  • commit으로 새로운 데이터가 실제 데이터베이스에 반영된다.
  • 이제 다른 세션에서도 회원 테이블을 조회하면 신규 데이터를 확인할 수 있다.

세션1 신규 데이터 추가 후 rollback
27

  • 세션1이 신규 데이터를 추가한 후에 commit 대신 rollback을 호출했다.
  • 세션1이 데이터베이스에 반영한 모든 데이터가 처음 상태로 복구된다.
  • 수정하거나 삭제한 데이터도 rollback을 호출하면 모두 트랜잭션을 시작하기 직전의 상태로 복구된다.


트랜잭션 - DB 예제2

자동 커밋, 수동 커밋

자동 커밋으로 설정하면 각각의 쿼리 실행 직후에 자동으로 커밋을 호출한다. 따라서, 커밋이나 롤백을 직접 호출하지 않아도 되는 편리함이 있다. 하지만 쿼리 하나를 실행할 때 마다 자동으로 커밋이 되어버리기 때문에 원하는 트랜잭션 기능을 제대로 사용할 수 없다.

자동 커밋 설정

set autocommit true; // 자동 커밋 모드 설정
insert into member(member_id, money) values ('data1', 10000); // 자동 커밋
insert into member(member_id, money) values ('data2', 10000); // 자동 커밋

따라서, commit, rollback을 직접 호출하면서 트랜잭션 기능을 제대로 수행하려면 자동 커밋을 끄고 수동 커밋을 사용해야 한다.

수동 커밋 설정

set autocommit false; // 수동 커밋 모드 설정
insert into member(member_id, money) values ('data3', 10000);
insert into member(member_id, money) values ('data4', 10000);
commit; // 수동 커밋

보통 자동 커밋 모드가 기본으로 설정된 경우가 많기 때문에, 수동 커밋 모드로 설정하는 것을 트랜잭션을 시작한다고 표현할 수 있다.
수동 커밋 설정을 하면 이후에 꼭 commit, rollback을 호출해야 한다.

[참고] : 수동 커밋 모드나 자동 커밋 모드는 한번 설정하면 해당 세션에서는 계속 유지된다. 중간에 변경하는 것은 가능하다.


트랜잭션 - DB 예제3

commit 예제

기본 데이터 입력

H2 데이터베이스 웹 콘솔 창을 2개 열어두고, 우선 기본 데이터를 넣어주었다.

기본 데이터
24

신규 데이터 추가 - 커밋 전

세션1 신규 데이터 추가
25

세션1 신규 데이터 추가 SQL

// 트랜잭션 시작
set autocommit false;
insert into member(member_id, money) values ('newId1', 10000);
insert into member(member_id, money) values ('newId2', 10000);

commit

세션1 신규 데이터 추가 후 commit
26

세션1에서 커밋 호출

commit; // 데이터베이스에 반영

rollback 예제

기본 데이터 입력

24

세션1 신규 데이터 추가

25

// 트랜잭션 시작
set autocommit false;
insert into member(member_id, money) values ('newId1', 10000);
insert into member(member_id, money) values ('newId2', 10000);

rollback

27

rollback; // 데이터베이스에 변경 사항을 반영하지 않는다.


트랜잭션 - DB 예제4

계좌이체 예제

  • 계좌이체 정상
  • 계좌이체 문제 발생 - 커밋
  • 계좌이체 문제 발생 - 롤백

계좌이체 정상

기본 데이터 입력
28

insert into member(member_id, money) values ('memberA', 10000);
insert into member(member_id, money) values ('memberB', 10000);

계좌이체 실행
29

  • memberA의 돈을 memberB에게 2000원 계좌이체하는 트랜잭션을 실행했다.
  • set autocommit false로 설정
  • 아직 커밋하지 않았으므로 다른 세션에서는 기존 데이터가 조회된다.
set autocommit false;
update member set money = 10000 - 2000 where member_id = 'memberA';
update member set money = 10000 + 2000 where member_id = 'memberB';

커밋
30

  • commit 명령어를 실행하면 데이터베이스에 결과가 반영된다.
  • 다른 세션에서도 memberA의 금액이 8000원으로 줄어들고, memberB의 금액이 12000원으로 증가한 것을 확인할 수 있다.

계좌이체 문제 발생 - 커밋

기본 데이터 입력
28

계좌이체 실행
31

set autocommit false;
update member set money = 10000 - 2000 where member_id = 'memberA'; // 성공
update member set money = 10000 + 2000 where member_iddd = 'memberB'; // 쿼리 예외
  • 계좌이체를 실행하는 도중에 SQL에 문제 발생.
    • memberA의 돈을 2000원 줄이는 것은 성공했지만, memberB의 돈을 2000원 증가시키는 것은 실패

강제 커밋
32

  • 이 상황에서 강제로 commit을 호출하게 되면 계좌이체는 실패하고, memberA의 돈만 2000원 줄어드는 심각한 문제가 발생한다.

계좌이체 문제 발생 - 롤백

기본 데이터 입력
28

계좌이체 실행
31

set autocommit false;
update member set money = 10000 - 2000 where member_id = 'memberA'; // 성공
update member set money = 10000 + 2000 where member_iddd = 'memberB'; // 쿼리 예외

롤백
33

문제가 발생했을 때는 롤백을 호출해서 트랜잭션을 시작하기 전 단계로 데이터를 복구해야 한다.
롤백을 사용한 덕분에 계좌이체를 실행하기 전 상태로 돌아왔다.


정리

원자성: 트랜잭션 내에서 실행한 작업들은 마치 하나의 작업인 것 처럼 모두 성공하거나 모두 실패해야 한다.
트랜잭션의 원자성 덕분에 여러 SQL 명령어를 마치 하나의 작업인 것 처럼 처리할 수 있었고, 덕분에 성공하면 한번에 반영하고, 중간에 실패해도 마치 하나의 작업을 되돌리는 것 처럼 간단히 되돌릴 수 있다.

오토 커밋
만약 오토 커밋 모드로 동작하는데 계좌이체 중간에 실패하게 되면, 오토 커밋은 쿼리를 실행할 때 마다 바로바로 커밋이 되어버리기 때문에 memberA의 돈만 2000원 줄어드는 문제가 발생한다.

트랜잭션 시작
따라서, 이런 종류의 작업은 꼭 수동 커밋 모드를 사용해서 수동으로 커밋, 롤백 할 수 있도록 해야한다. 보통 이렇게 자동 커밋 모드에서 수동 커밋 모드로 전환하는 것을 트랜잭션을 시작한다고 표현한다.


<출처 : 인프런 - 스프링 DB 1편 : 데이터 접근 핵심 원리(김영한)>

댓글남기기