트랜잭션 격리레벨, 락, 전파타입 정리
May 27, 2021
1. 트랜잭션
하나의 논리적인 작업 셋 자체가 100% 적용되거나 또는 아무것도 적용되지 않아야 함을 보장해 주는 것.
2. 트랜잭션의 성질
ACID
- 원자성(Atomicity) - 트랜잭션 내에서 실행한 작업들은 모두 성공하거나 모두 실패해야한다.
- 일관성(Counsistency) - 일관성 있는 데이터베이스 상태를 유지해야 한다. (무결성 제약 조건 만족)
- 격리성(Isolation) - 동시에 실행되는 트랜잭션들이 서로에게 영향을 미치지 않도록 격리한다.
- 지속성(Durability) - 트랜잭션을 성공적으로 끝내면 그 결과가 항상 기록되어야 한다.
3. 트랜잭션 격리레벨
- 트랜잭션은 원자성, 일관성, 지속성을 보장한다
- 문제는 격리성 - 격리성을 완벽히 보장하려면 거의 차례대로 실행해야 하는데 이러면 동시성 처리 성능이 매우 나빠진다.
- ANSI 표준은 트랜잭션 격리 수준을 4단계로 나누어 정의했다.
- READ UNCOMMITED(커밋되지 않은 읽기)
- READ COMMITED(커밋된 읽기)
- REPEATABLE READ(반복 가능한 읽기)
- SERIALIZABLE(직렬화 가능)
격리수준 | DIRTY READ | NON-REPEATABLE READ | PHANTOM READ |
---|---|---|---|
READ UNCOMMITTED | O | O | O |
READ COMMITTED | O | O | |
REPEATABLE READ | O | ||
SERIALIABLE |
-
DIRTY READ
- 커밋되지 않은 데이터를 읽을 수 있는 문제
- 트랜잭션 2가 Dirty read한 데이터를 사용하는데 트랜잭션 1이 롤백되면 심각한 데이터 정합성 문제가 발생할 수 있다.
-
NON-REPEATABLE READ
- 반복해서 같은 데이터를 읽을 수 없는 상태
- 트랜잭션 일관성에 위배
-
PHANTOM READ
- 반복 조회시 결과 집합이 달라지는 것
(참고 1) READ COMMITTED와 REPEATABLE READ의 성능 비교
- 실제 온라인 서비스 상황에서는 의도적인 경우가 아니라면 NON REPEATABLE READ의 발생 가능성이 거의 없다.
- 의도적으로 NON REPEATABLE READ를 발생시키는 대표적인 예
- 하나의 트랜잭션을 열어 그 트랜잭션에서 모든 테이블의 데이터를 SELECT한 후, 그대로 계속 놔두면 Undo 영역이 계속 커져서 시스템 테이블스페이스의 I/O가 유발되는 경우
- Real MySQL에 제시된 벤치마크 결과에 따르면
- 1G, 30GB 크기의 테이블에서는 REPEATABLE READ가 2% 정도 높은 성능을 보임
- 100GB 크기의 테이블에서는 READ COMMITTED가 7% 정도 높은 성능을 보임
(참고 2) JPA를 사용할 때의 트랜잭션 격리레벨
- Jpa의 영속성 컨텍스트(1차 캐시)를 적절히 활용하면 DB 트랜잭션이 READ COMMITTED 격리 수준이어도 애플리케이션 레벨에서 REPEATABLE READ가 가능하다.
- 만약 일부 로직에 더 높은 격리 수준이 필요하면 낙관적 락이나 비관적 락을 사용하면 된다.
4. 낙관적 락(Optimistic Locking)과 비관적 락(pessimistic Locking)
- 낙관적 락
- 각 트랜잭션이 같은 레코드를 변경할 가능성은 상당히 희박할 것이라고(낙관적으로) 가정한다.
- 우선 변경 작업을 수행하고 잠금 충돌이 있었는지 확인해 문제가 있었다면 롤백처리한다.
- 비관적 락
- 현재 변경하고자 하는 레코드를 다른 트랜잭션에서도 변경할 수 있다고(비관적으로) 가정한다.
- 변경하고자 하는 레코드에 대해 잠금을 획득한 이후 변경작업을 처리한다.
- 높은 동시성 처리에는 비관적 잠금이 유리하다고 알려져 있으며 InnoDB는 비관적 잠금 방식을 채택하고 있다.
5. 트랜잭션 전파타입 (스프링)
@Transactional의 옵션중 propagation을 통해 설정
진행 중인 트랜잭션 O | 진행 중인 트랜잭션 X | |
---|---|---|
REQUIRED (기본값) | 해당 트랜잭션 사용 | 새로운 트랜잭션 생성 |
MANDATORY | 해당 트랜잭션 사용 | 예외 발생 |
REQUIRES_NEW | 해당 트랜잭션 보류, 새로운 트랜잭션 생성 | 새로운 트랜잭션 생성 |
NESTED | 중첩 트랜잭션 생성 | 새로운 트랜잭션 생성 |
SUPPORTS | 해당 트랜잭션 사용 | 트랜잭션 없이 진행 |
NOT_SUPPORTED | 해당 트랜잭션 보류 | 트랜잭션 없이 진행 |
NEVER | 예외 발생 | 트랜잭션 없이 진행 |
- 진행중인 트랜잭션이 있을 때 REQUIRES_NEW와 NESTED의 차이
- REQUIRES_NEW - 각각의 트랜잭션이 롤백되더라도 서로 영향을 주지 않는다.
- NESTED - 중첩 트랜잭션 내부에서 롤백 발생시 해당 중첩 트랜잭션의 시작 지점까지만 롤백 된다. 중첩 트랜잭션은 부모 트랜잭션이 커밋될 때 같이 커밋된다.
(참고) 고객 주문 + 주문 내역을 기록해주는 기능을 개발한다면?
- 중요한 일(주문 내역), 중요하지 않은 일(로그)을 별개의 트랜잭션으로 관리
- 주문이 실패하면 로그도 기록되지 않아야 하지만, 주문이 성공했는데 로그 실패 때문에 전체 롤백이 되면 안된다.
- 부모 트랜잭션의 커밋, 롤백 -> 중첩 트랜잭션에 영향을 줌
- 중첩된 트랜잭션의 커밋, 롤백 -> 부모 트랜잭션에 영향을 주지 않음
- 주문은 부모 트랜잭션으로 설정하고, 로그는 중첩 트랜잭션으로 구현
참고자료
- Real MySQL
- 자바 ORM 표준 JPA 프로그래밍
- 10분 테코톡 - 예지니어스의 트랜잭션
- 프로그래밍 초식 : DB 트랜잭션 조금 이해하기 02 격리
- Spring 트랜잭션의 전파 설정별 동작
- 트랜잭션 전파 속성 ( propagation ), 롤백 예외