ACID
트랜잭션(transaction)
트랜잭션
데이터를 저장할때 DB에 저장하는 가장큰 이유중 하나는 DB는 트랜잭션이라는 개념을 지원하기 때문이다.
- A 계좌에서 출금
- B 계좌에 입금
@Transactional
애너테이션이 붙은 메서드에서는 해당 메서드 내의 모든 작업이 하나의 데이터베이스 트랜잭션 내에서 실행된다. 즉 위 와같은 예시에서 A의 계좌에서 출금이 된후 B계좌에 입금이 되는 작업을 하나로 묶어 처리한다는 뜻이다. 이 이유는 데이터 베이스 작업을 atomic으로 만들기 위함이다.
이때 1번 작업은 성공했는데 2번 작업이 실패한다면 롤백(Rollback)을 하면되고 1,2번 작업이 모두 성공하면 DB에 정상 반영하는 커밋(Commit)을 하게된다.
ACID
원자성(Atomicity) : 트랜잭션 내에서 실행한 작업은 모두 성공하거나 모두 실패해야한다.
일관성(Consistenct): 모든 트랜잭션은 일관성 있는 DB상태를 유지해야한다.
격리성(Isolation): 동시에 실행되는 트랜잭션들이 서로에게 영향을 미치지 않도록 격리한다.
지속성(Durability): 트랜잭션을 성공적으로 끝내면 그 결과가 항상 기록되어야 한다. 즉 중간에 문제가 발생하더라도 DB의 로그 등을 사용해 성공한 트랜잭션 내용을 복구해야 한다.
❗️격리성을 완전히 보장하려면 트랜잭션을 거의 순서대로 실행해야 한다. 이렇게 하면 동시 처리 성능이 매우 나빠진다. 따라서 이런 문제를 해결하기 위해 ANSI 표준은 트랜잭션의 격리 수준을 4단계로 나누어 정의하며 이 4단계중 한단계를 선택할수 있다.
트랜잭션 격리 수준 - Isolation level
READ UNCOMMITED(커밋되지 않은 읽기)
- 이 수준에서는 다른 트랜잭션에서 커밋되지 않은 변경사항(즉, 아직 확정되지 않은 변경사항)도 조회할 수 있다. 이를
Dirty Read
라고 한다. - ex) 트랜잭션 A가 데이터 X를 수정하고 아직 커밋하지 않았다. 트랜잭션 B는 이 수정된(하지만 아직 커밋되지 않은) 데이터 X를 조회할 수 있다.
- 이 수준에서는 다른 트랜잭션에서 커밋되지 않은 변경사항(즉, 아직 확정되지 않은 변경사항)도 조회할 수 있다. 이를
READ COMMITTED(커밋된 읽기)
- 이 수준에서는 오직 커밋된 변경사항만 조회할 수 있다. 즉, 다른 트랜잭션에서 커밋되지 않은 변경사항은 보이지 않는다.
- ex) 트랜잭션 A가 데이터 X를 수정하고 아직 커밋하지 않았다. 트랜잭션 B는 이전 커밋된 상태의 데이터 X를 조회한다. A가 커밋한 후에 B가 다시 조회하면 변경된 X를 볼 수 있다.
REPEATABLE READ(반복 가능한 읽기)
- 이 수준에서는 트랜잭션이 시작될 때 조회한 데이터의 일관된 상태를 트랜잭션 내내 유지한다. 다른 트랜잭션이 해당 데이터를 변경하더라도, 이 트랜잭션에서는 변경사항이 반영되지 않는다.
- ex) 트랜잭션 A가 데이터 X를 조회한다. 이후 트랜잭션 B가 데이터 X를 변경하고 커밋한다. 하지만 트랜잭션 A는 여전히 최초 조회했던 시점의 데이터 X를 볼 수 있다.
SERIALIZABLE(직렬화 가능)
- 가장 엄격한 격리 수준으로, 트랜잭션들이 서로에게 아무런 영향을 주지 않는 것처럼 실행된다. 이 수준에서는 동시성이 크게 제한되며, 성능 저하가 발생할 수 있다.
- ex) 트랜잭션 A가 데이터 X와 Y를 조회한다. 이 수준에서는 트랜잭션 B가 X 또는 Y에 영향을 미치는 어떠한 작업도 수행할 수 없다. A가 완료될 때까지 B는 X나 Y와 관련된 작업을 수행할 수 없다.
아래로 내려갈수록 성능이 나빠지지만 더 안전하게 사용할수 있다.
이 강의는 READ COMMITTED(커밋된 읽기) 트랜잭션 격리 수준을 기준으로 설명한다고 한다.
데이터 베이스 연결 구조와 DB 세션
개발자가 클라이언트를 통해 SQL을 전달하면 현재 커넥션에 연결된 세션이 SQL 문을 실행하며, 이때 세션은 트랜잭션을 시작하고, 커밋 또는 롤백을 통해 트랜잭션을 종료한다. 그리고 이후에 새로운 트랜잭션을 다시 시작할수도 있다.
트랜잭션 개념 이해
데이터 변경 쿼리를 실행하고 그 결과를 반영하려면 커밋 명령어인 commit
을 호출하고, 결과를 반영하고 싶지 않으면 rollback
명령어를 호출하면 된다.
즉 commit
명령어를 호출하기 전까진 임시로 데이터를 저장한다. 따라서 해당 트랜잭션을 시작한 세션(사용자) 에게만 변경 데이터가 보이고 다른 세션(사용자)에게는 변경 데이터가 보이지 않는다.
Commit
위처럼 세션1에서 신규 회원을 추가한다.
이때 commit
을 하지 않은 상태라면 세션2에서 테이블 조회를 한 결과는 오른쪽 table과 같다.
즉 세션1 에서 아직 commit
을 하지 않았으므로 세션 2에서 테이블 조회시 아직 신규 회원 데이터가 보이지 않는다.
그후 세션 1에서 commit
을 하게 된다면 그후엔 세션2에서도 테이블 조회시 추가된 신규 회원 데이터가 보이게 된다.
Rollback
만약 Rollback
을 호출하게되면 트랜잭션을 시작한 상태로 돌아가게 된다.
즉 세션1이 DB에 반영한 모든 데이터가 처음 상태로 복구된다.
자동 커밋, 수동 커밋
트랜잭션 사용시 자동커밋 또는 수동 커밋을 설정할수 있다.
기본값은 자동 커밋이다.
자동 커밋 사용시 우리가 따로 설정할 필요없이 sql 문을 자동적으로 커밋해준다.
하지만 자동커밋 사용시 자동으로 커밋이 일어나버리기 때문에 트랜잭션 기능을 제대로 사용할수 없다.
수동커밋
set autocommit false; //수동 커밋 모드 설정
insert into member(member_id, money) values ('data3',10000);
insert into member(member_id, money) values ('data4',10000);
commit; //수동 커밋
위처럼 수동커밋을 설정해줄수있다. (관례상 수동커밋 설정이 트랜잭션의 시작을 의미한다.)
수동커밋을 설정하면 꼭 마지막에 commit
또는 rollback
을 호출해줘야 한다.
❗자동 커밋을 사용하면 결국 sql 문을 작성후 commit 또는 rollback 을 하더라도 아무런 일도 발생하지 않는다. (각각의 sql마다 자동적으로 commit이 되기 때문에)
DB 락
만약 세션1,2 가 동시에 어떠한 데이터를 동시에 수정하려한다면 트랜잭션의 원자성이 깨질수 있다.
이런 문제를 방지하기위해 하나의 세션이 특정 데이터를 수정하고 커밋이나 롤백 전까지 다른세션에서 해당 데이터를 수정할수 없게 막아야 한다.
이렇게 세션1이 세션2보다 먼저 락을 획득한다면, 세션1은 해당 row에 update를 할수있다.
이때 세션2또한 같은 데이터를 수정하기위해선 lock을 획득해야하지만 세션1이 먼저 lock을 획득후 동작하고 있으므로 세션1의 트랜잭션이 commit될때까지 기다려야한다.
❗️참고로 세션2가 무한정 기다리는건 아니다. 락 대기시간이 넘어가면 타임아웃 오류가 발생한다.
조회할때 lock을 사용하는 특수한 경우
조회는 기본적으로 세션1이 lock을 획득하고 데이터를 수정하고 있더라도 다른 세션에서 해당 데이터 조회를 할수있다.
만약 데이터를 조회하는데 다른 세션에서 해당 데이터를 조작하는걸 원치 않는다하면 select for update
구문을 사용하면 된다.
set autocommit false;
select * from member where member_id='memberA' for update;
위와같이 sql 구문을 작성한다면 다른 세션에서 해당 데이터를 조작할수 없게된다. (lock 을 가져와 해당 데이터의수정을 막는거기 때문에 조회까지 안되는건 아니다.)
결론적으로 select for update
구문을 사용하면, 내가 조회할때 다른 세션에서 해당 데이터를 조작할수는 없지만 조회는 가능하다.
Reference
'DB' 카테고리의 다른 글
Docker를 통해 Mysql 설치 (1) | 2023.11.27 |
---|---|
h2 DB 연결 방법 (1) | 2023.11.27 |
커넥션 풀 및 데이터 소스 (0) | 2023.10.25 |
JDBC (0) | 2023.10.25 |
MySQL) 프로시저를 사용하여 더미 데이터 만들기 (0) | 2023.05.04 |