모든 명령어의 성공 또는 실패 를 한꺼번에 모아서 처리 하는 것
명령어 중에 하나라도 실패하면 한 트랜잭션 내의 모든 명령어들이 무효화 (롤백) 된다.
은행에서 돈을 송금하려고 할 때
① 내 계좌에서 돈을 출금
② 다른 계좌로 돈을 입금
하는 과정이 함께 일어 나야 하는데 2번 과정에서 에러가 발생해서 입금이 되지 않았다면 돈이 붕 떠버리는 사태가 일어나게 됨
➡️ 이 경우엔 ①, ② 를 한 트랜잭션으로 묶어서 하나의 과정이라도 실패하면 롤백 되도록 해야함
트랜잭션의 성질에는 총 4가지가 있다.
A tomicity (원자성)
트랜잭션은 부분적으로 완료 될 수 없으며, 전체가 다 되거나 아무것도 되지 않거나 둘 중 하나여야 한다
-> 출금하는 것만 성공하고 입금 되는 건 성공하면 안된다.
C onsistency (일관성)
트랜잭션이 실행을 성공적으로 완료하면 언제나 일관성 있는 데이터베이스 상태로 유지
-> 계좌 이체 하기 전 진행 전의 돈의 합과 진행 후의 돈의 합이 같아야 한다.
I solation (독립성,격리성)
트랜잭션을 수행 시 다른 트랜잭션의 연산 작업이 끼어들지 못하도록 보장
-> 출금 후 다른 트랜잭션이 끼어 들어 내 계좌에 남아 있는 금액을 보지 못해야 한다.
D urablility (영속성,지속성)
성공적으로 수행된 트랜잭션은 영원히 반영
-> 완료 후 시스템 문제가 생기더라도 커밋이 되었다면 DB 에 영구적으로 반영되어야 한다.
하지만 ACID 원칙을 완벽하게 지키게 되면 동시성이 매우 떨어지기 때문에 완벽하게 지켜지지 않는 경우도 있다.
그렇기 때문에 ACID 원칙을 희생하여 동시성을 얻을 수 있는 방법인 isolation Level (격리 수준) 을 제공한다.
격리 수준은 3가지 문제가 발생하는지에 따라 분류 한다.
① Dirty Read ② Non-Repeatable Read ③ Phantom Read
① Dirty Read (비커밋 읽기)
트랜잭션에서 커밋되기 전 작업 내용을 다른 트랜잭션에서 볼 수 있다
EX) A 라는 트랜잭션에서 id : 22, name: 이름을 insert 하고 커밋하지 않은 상태에서 B 라는 트랜잭션이 id: 22 를 select 하는 경우 읽을 수 있음. 이때 A 트랜잭션에서 커밋되지 않고 롤백되는 경우 데이터 정합성에 문제가 생긴다.
👉 가능한 이유
InnoDB 엔진이 transaction 을 커밋하는 방식 때문에 가능하다.
InnonDB 는 일단 commit 이 되지 않더라도 실행한 모든 쿼리를 DB 에 적용한다.
즉, 특별히 log를 보고 특정 시점의 snapshot을 복구하는 consistent read를 하지 않고 그냥 해당 시점의 DB 를 읽으면 dirty read 로 간주한다.
consistent read 란 ?
select 시 현재 DB 값이 아닌 특정 시점의 DB snapshot 을 읽어 오는 것
이 snapshot 은 commit 된 변화만이 저장된 것을 의미한다.
InnoDB 엔진은 각 쿼리를 실행할 때마다 실행한 쿼리의 log를 차곡차곡 저장한다.
그리고 나중에 consistent read를 할 때 이 log를 통해 특정 시점의 DB snapshot을 복구하여 가져온다.
② Non-Repeatable Read (비반복 읽기)
한 트랜잭션에서 같은 쿼리를 두번 실행시 그 사이 다른 트랜잭션이 값을 수정, 삭제 해서 두 쿼리의 결과가 상이하게 나타나는 현상을 의미한다.
③ Phantom Read (가상 읽기)
한 트랜잭션 안에서 일정 범위의 레코드를 두번 이상 읽을 때, 첫번째 쿼리에서 없던 레코드가 두번째 쿼리에서 나타나는 현상을 의미한다.
위의 3가지 문제가 발생하는지 여부에 따라서 트랜잭션 격리 수준이 결정되는데, 그건 아래 표와 같이 결정된다. ⬇️
분리 레벨 | Dirty Read | Non-Repeatable Read | Phantom Read |
---|---|---|---|
READ UNCOMMITEED | O | O | O |
READ COMMITTED | - | O | O |
REPEATABLE READ | - | - | O |
SERIALIZABLE | - | - | - |
Dirty Read
/ Non-Repeatble Read
/ Phantom Read
가 발생한다. Non-Repeatable Read
/ Phantom Read
가 발생한다. Phantom Read
가 발생한다. 위에서 이론에 대해서 알았으니, 이제 실질적으로 Rails 와 Spring 에서 트랜잭션을 어떻게 적용하면 되는지 알아보자
ActiveRecord::Base.transaction do ## 예외 발생 시 롤백
rescue => e ## 예외 발생한 경우 처리
end
위 코드처럼 트랜잭션을 사용하면 되고, 만약 트랜잭션 격리 수준을 직접 설정해주고 싶은 경우에는 아래 처럼 isolation 을 직접 지정해주면 된다.
transaction(isolation: :?)
:read_uncommited / :read_commited / :repeatable_read / :serializable
@Transactional
스프링에선 해당 어노테이션을 메소드, 클래스 위에 사용하면 트랜잭션을 사용할 수 있다.
스프링의 경우에도 @Transactional(isolation=Isolation.SERIALIZABLE) 로 격리 수준을 직접 설정해줄 수 있다.