[SpringDB1] 트랜잭션 개념
개념 이해
데이터베이스는 트랜잭션이라는 개념을 지원하는데, 트랜잭션은 데이터베이스에서 하나의 거래를 안전하게 처리하도록 보장해주는 것을 뜻한다.
5000원 계좌이체 예시
- A의 잔고를 5000원 감소
- 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
- 사용자는 WAS나 DB 접근 툴 같은 클라이언트를 사용해서 데이터베이스 서버에 접근할 수 있다. 클라이언트는 데이터베이스 서버에 연결을 요청하고 커넥션을 맺게 된다. 이때 데이터베이스 서버는 내부에 세션이라는 것을 만든다. 그리고 앞으로 해당 커넥션을 통한 모든 요청은 이 세션을 통해서 실행하게 된다.
- 쉽게 풀자면 개발자가 클라이언트를 통해 SQL을 전달하면 현재 커넥션에 연결된 세션이 SQL을 실행한다.
- 세션은 트랜잭션을 시작하고, 커밋 또는 롤백을 통해 트랜잭션을 종료한다. 그리고 이후에 새로운 트랜잭션을 다시 시작할 수 있다.
- 사용자가 커넥션을 닫거나, 또는 DB관리자가 세션을 강제로 종료하면 세션은 종료된다.
데이터베이스 연결 구조2
- 커넥션 풀이 10개의 커넥션을 생성하면, 세션도 10개 만들어진다.
트랜잭션 - DB 예제1
개념 이해
트랜잭션 사용법
- 데이터 변경 쿼리를 실행하고 데이터베이스에 그 결과를 반영하려면
commit
을 호출하고, 결과를 반영하고 싶지 않으면rollback
을 호출하면 된다. - 커밋을 호출하기 전까지는 임시로 데이터를 저장하는 것이다. 따라서, 해당 트랜잭션을 시작한 세선(사용자)에게만 변경 데이터가 보이고, 다른 세션(사용자)에게는 변경 데이터가 보이지 않는다.
- 등록, 수정, 삭제 모두 같은 원리로 동작한다.
기본 데이터
- 세션1, 세션2 모두 기본 테이블을 조회하면 해당 데이터가 그대로 조회된다.
세션1 신규 데이터 추가
- 세션1은 트랜잭션을 시작하고 신규 데이터를 DB에 추가했으며
commit
은 하지 않았다. - 새로운 데이터는 임시 상태로 저장된다.
- 세션1은
select
쿼리를 실행해서 본인이 입력한 신규 데이터를 조회할 수 있다. - 세션2는
select
쿼리를 실행해도 신규 데이터를 확인할 수 없다.(세션1이commit
하지 않았기 때문)
커밋하지 않은 데이터를 다른 곳에서 조회할 수 있다면?
세션2에서 세션1이 아직 커밋하지 않은 변경 데이터가 보인다면, 세션1이 롤백 했을 때 심각한 문제가 발생할 수 있기 때문에, 커밋 전의 데이터는 다른 세션에서 보이지 않는다.
세션1 신규 데이터 추가 후 commit
- 세션1이 신규 데이터를 추가한 후에
commit
을 호출했다. commit
으로 새로운 데이터가 실제 데이터베이스에 반영된다.- 이제 다른 세션에서도 회원 테이블을 조회하면 신규 데이터를 확인할 수 있다.
세션1 신규 데이터 추가 후 rollback
- 세션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개 열어두고, 우선 기본 데이터를 넣어주었다.
기본 데이터
신규 데이터 추가 - 커밋 전
세션1 신규 데이터 추가
세션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
세션1에서 커밋 호출
commit; // 데이터베이스에 반영
rollback 예제
기본 데이터 입력
세션1 신규 데이터 추가
// 트랜잭션 시작
set autocommit false;
insert into member(member_id, money) values ('newId1', 10000);
insert into member(member_id, money) values ('newId2', 10000);
rollback
rollback; // 데이터베이스에 변경 사항을 반영하지 않는다.
트랜잭션 - DB 예제4
계좌이체 예제
- 계좌이체 정상
- 계좌이체 문제 발생 - 커밋
- 계좌이체 문제 발생 - 롤백
계좌이체 정상
기본 데이터 입력
insert into member(member_id, money) values ('memberA', 10000);
insert into member(member_id, money) values ('memberB', 10000);
계좌이체 실행
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';
커밋
commit
명령어를 실행하면 데이터베이스에 결과가 반영된다.- 다른 세션에서도
memberA
의 금액이 8000원으로 줄어들고,memberB
의 금액이 12000원으로 증가한 것을 확인할 수 있다.
계좌이체 문제 발생 - 커밋
기본 데이터 입력
계좌이체 실행
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원 증가시키는 것은 실패
강제 커밋
- 이 상황에서 강제로
commit
을 호출하게 되면 계좌이체는 실패하고,memberA
의 돈만 2000원 줄어드는 심각한 문제가 발생한다.
계좌이체 문제 발생 - 롤백
기본 데이터 입력
계좌이체 실행
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원 줄어드는 문제가 발생한다.
트랜잭션 시작
따라서, 이런 종류의 작업은 꼭 수동 커밋 모드를 사용해서 수동으로 커밋, 롤백 할 수 있도록 해야한다. 보통 이렇게 자동 커밋 모드에서 수동 커밋 모드로 전환하는 것을 트랜잭션을 시작한다고 표현한다.
댓글남기기