✔ 깃허브 소스코드
✔ Udemy 강의영상
데이터베이스의 상태를 변환시키는 하나의 논리적인 작업 단위를 구성하는 연산들의 집합입니다.
예를 들어 A 계좌에서 B 계좌로 일정 금액을 이체한다고 가정합시다.
이러한 과정들이 모두 합쳐져서 "계좌 이체" 라는 하나의 작업 단위를 구성하게 됩니다.
Commit 연산
하나의 논리적 단위(트랜잭션)에 대한 작업이 성공적으로 끝나 데이터베이스가 다시 일관된 상태에 있을 때, 이 트랜잭션이 행한 갱신 연산이 완료된 것을 트랜잭션 관리자에게 알려주는 연산입니다.
Rollback 연산
하나의 트랜잭션 처리가 비정상적으로 종료되어 데이터베이스의 일관성을 깨뜨렸을 때, 이 트랜잭션의 일부가 정상적으로 처리되었더라도 트랜잭션의 원자성을 구현하기 위해 이 트랜잭션이 행한 모든 연산을 취소(Undo)하는 연산입니다.
Rollback 시에는 해당 트랜잭션을 재시작하거나 폐기합니다.
원자성(Atomicity), All or Nothing
트랜잭션의 모든 연산들은 정상적으로 수행 완료되거나 아니면 전혀 어떠한 연산도 수행되지 않은 상태를 보장해야 합니다.
일관성(Consistency)
트랜잭션 완료 후에도 데이터베이스가 일관된 상태로 유지되어야 합니다.
독립성(Isolation)
하나의 트랜잭션이 실행하는 도중에 변경한 데이터는 이 트랜잭션이 완료될 때까지 다른 트랜잭션이 참조하지 못합니다.
지속성(Durability)
성공적으로 수행된 트랜잭션은 영원히 반영되어야 합니다.
격리 수준(Isolation Level)이란?
트랜잭션에서 일관성이 없는 데이터를 허용하도록 하는 수준
격리 수준의 필요성
데이터베이스는 ACID 속성에 따라 트랜잭션이 원자적이면서도 독립적인 수행을 하도록 합니다. 그래서 Locking
이라는 개념이 등장하게 됐습니다. Locking
은 하나의 트랜잭션이 데이터베이스를 다루는 동안 다른 트랜잭션이 관여하지 못하게 막는 것입니다. 하지만 무조건적인 Locking
으로 인해 동시에 수행되는 많은 트랜잭션들을 순서대로 처리하는 방식으로 구현되면 데이터베이스의 성능은 떨어지게 됩니다. 반대로 응답성을 높이기 위해 Locking
범위를 줄인다면 잘못된 값이 처리 될 여지가 있습니다. 그래서 최대한 효율적인 Locking
방법이 필요합니다.
Read Uncommitted(Level 0)
SELECT
문장이 수행되는 동안 해당 데이터에 Shared Lock
이 걸리지 않는 레벨Read Committed(Level 1)
SELECT
문장이 수행되는 동안 해당 데이터에 Shared Lock
이 걸리는 레벨Repeatable Read(Level 2)
SELECT
문장이 사용하는 모든 데이터에 Shared Lock
이 걸리는 레벨Serializable(Level 3)
SELECT
문장이 사용하는 모든 데이터에 Shared Lock
이 걸리는 레벨Dirty Read
Non-Repeatable Read
한 트랜잭션에서 같은 쿼리를 두 번 수행할 때 그 사이에 다른 트랜잭션이 값을 수정 또는 삭제함으로써 두 쿼리의 결과가 상이하게 나타나는 비 일관성 현상입니다.
예시
SELECT * FROM person WHERE id = 10;
UPDATE person SET age = 30 WHERE id = 10;
SELECT * FROM person WHERE id = 10;
Phantom Read
한 트랜잭션 안에서 일정 범위의 레코드를 두 번 이상 읽을 때, 첫 번째 쿼리에서 없던 레코드가 두 번째 쿼리에서 나타나는 현상입니다.
이는 트랜잭션 도중 새로운 레코드가 삽입되는 것을 허용했기 때문에 나타납니다.
예시
SELECT * FROM person WHERE age BETWEEN 5 AND 55;
INSERT INTO person VALUES(13, 'Alex', 25);
SELECT * FROM person WHERE age BETWEEN 5 AND 55;
Serializable
을 선택하게 되면 문제가 되는 현상들은 발생하지 않지만 성능이 굉장히 떨어집니다. 왜냐하면 병렬적으로 트랜잭션이 실행되더라도 모두 Commit 이 될 때까지 기다려야하므로 사실상 직렬적으로 실행된다고 볼 수가 있습니다.
대부분의 애플리케이션에서는 Read Committed
을 사용합니다. Locking 적절히 사용하므로 성능면에서도 준수한 편이고 데이터베이스의 일관성에 대해서도 준수한 편입니다.
만약 하나의 트랜잭션에서 하나의 데이터베이스나 MQ를 사용하게 되면 JPA의 @Transactional
을 사용해도 무방합니다. 그러나 하나의 트랜잭션에서 하나 이상의 데이터베이스나 MQ를 사용해서 수정이나 읽기 연산 등을 수행하게 되면 Spring의 @Transactional
을 사용해주어야 합니다.
격리 수준 설정
@Transactional(isolation = Isolation.READ_COMMITTED)
또는
READ_UNCOMMITTED
= 1READ_COMMITTED
= 2REPEATABLE_READ
= 4SERIALIZABLE
= 8spring.jpa.properties.hibernate.connection.isolation=2