들어가며
트랜잭션이란 DB에서 하나의 기능을 수행하기 위한 작업의 단위 이다.
여러 쿼리들을 하나로 묶는 단위이다.
커밋이 되었다 == 하나의 트랜잭션이 성공적으로 수행되었다.
트랜잭션 정의 이유
- DB에서 데이터를 다룰때 장애가 일어날 때 데이터를 복구하는 작업의 단위
- 여러 작업 동시에 같은 데이터를 다룰 때 작업을 서로 분리하는 단위가 됨
트랜잭션은 전체가 수행되거나 전혀 수행되지 않아야함 (ALL OR NOTHING)
트랜잭션의 ACID 특징
원자성
일부만 수행되는 일이 없도록 전부 수행되거나 아예 수행되지 않아야함 all or nothing일관성
- 트랜잭션은 db의 일관성을 유지해야함
일관성
트랜잭션을 수행하기 전이나 수행한 후나 db는 항상 일관된 상태를 유지해야함
- 일관 되게 조건,규칙에 유효해야함
격리성
- 여러 트랜잭션이 동시에 수행되는데 상호 존재를 모르고 독립적으로 수행됨
- 고립성 유지위해선 트랜잭션이 변경중인 임시데이터를 다른 트랜잭션이 읽고 쓸때 제어가필요함
- 동시성 제어
이를 위한 여러 격리수준이 존재함 추후 서술
지속성
- 트랜잭션이 정상적으로 commit 혹은 부분완료 한 데이터는 dbms가 책임지고 db에 기록
격리 수준
여러 트랜잭션은 순차적으로 실행되는 것 처럼 작동하고, 여러 사용자가 같은 데이터에 접근할 수 있어야한다.
순차적으로 하면 쉽지만, 성능이 안좋다.
SERIALIZABLE
격리성은 강한데, 동시성은 약하다.
TABLE 전체에 락을 건다.
트랜잭션이 완료될 때까지 다른 사용자는 그 영역에 해당되는 데이터에 대한 수정 및 입력이 불가능하다.
@Transactional(isolation = Isolation.SERIALIZABLE)
쓰기, 읽기에도 잠금이 걸리기 때문에 다른 트랜잭션에서는 레코드를 변경하지 못한다. 그래서 동시처리 능력이 다른 격리수준보다 떨어지고, 성능저하가 발생하게 된다.
교착상태가 발생할 수 있다.
REPEATABLE_READ
하나의 트랜잭션이 수정한 행에 공유락, 베타락을 설정하여 다른 트랜잭션이 수정할 수 없다.
TABLE의 행에 LOCK을 건다.
하지만 새로운 행을 추가할 수 있다. 이후 추가된 행이 발견될 수 있다.
@Transactional(isolation = Isolation.REPEATABLE_READ)
문제점
팬텀 리드 발생
- 동일 쿼리 보냈을 경우 조회 결과가 다르다.
- 이로 인해 데이터의 일관성이 깨질 수 있다.
- 예상치 못한 값이 반환된다.
해결 ?
- 팬텀 리드를 방지하기 위해 트랜잭션 범위 내에서 데이터에 락을 걸어 다른 트랜잭션의 간섭을 막는다.
- Serializable 격리 수준을 사용한다.
- Optimistic Locking은 데이터 변경 시 버전 번호 등의 필드를 사용하여 충돌을 탐지하고, 충돌이 발생하면 트랜잭션을 롤백하는 방식으로 팬텀 리드를 방지할 수 있습니다.
READ_COMMITTED
MYSQL 기본값이다.
자신의 데이터에 공유락을 건다.
다른 트랜잭션이 커밋하지 않은 정보는 읽을 수 없다. 커밋 완료된 데이터에 대해서만 조회를 허용한다.
@Transactional(isolation = Isolation.READ_COMMITTED)
트랜잭션이 접근한 행을 다른 트랜잭션이 수정할 수 있다.
문제점
반복 가능하지 않은 조회
- 한 트랜잭션 내의 같은 행에 두번 이상의 조회가 발생했을때 값이 다른 경우
- 이로 인해 데이터의 일관성이 깨질 수 있다.
- 예상치 못한 값이 반환된다.
READ_UNCOMMITTED
하나의 트랜잭션이 커밋되기 전에 다른 트랜잭션에 노출된다.
데이터 부정합 문제가 발생할 확률이 높지만, 성능은 가장 빠르다.
데이터를 어림잡아 집계할 때 사용하면 좋다.
왜 문제가 생길 수 있음에도 READ_COMMITTED를 주로 사용할까 ?
데이터베이스 트랜잭션의 동시성과 일관성 사이에서 트레이드 오프를 이루기 위한 선택적인 격리 수준이다.
트랜잭션 전파
트랜잭션은 connection 단위로 수행하기 때문에 같은 트랜잭션일 경우 커넥션을 넘겨주어 수행 해야한다.
이를 넘겨서 수행하지 않고 여러 메소드가 한 트랜잭션 내에 묶이는 것을 트랜잭션 전파 라고 한다.
@Transactional(propagation = Propagation.REQUIRED)
부모 트랜잭션이 존재한다면 부모 트랜잭션으로 합류한다. 부모 트랜잭션이 없다면 새로운 트랜잭션을 생성한다.
중간에 롤백이 발생한다면 모두 하나의 트랜잭션이기 때문에 진행사항이 모두 롤백된다.
@Transactional(propagation = Propagation.REQUIRES_NEW)
무조건 새로운 트랜잭션을 생성합니다. 각각의 트랜잭션이 롤백되더라도 서로 영향을 주지 않는다.
@Transactional(propagation = Propagation.MANDATORY)
부모 트랜잭션에 합류한다. 만약 부모 트랜잭션이 없다면 예외를 발생시킨다.
@Transactional(propagation = Propagation.NESTED)
부모 트랜잭션이 존재한다면 중첩 트랜잭션을 생성한다.
중첩된 트랜잭션 내부에서 롤백 발생시 해당 중첩 트랜잭션의 시작 지점 까지만 롤백되고 부모 트랜잭션에는 영향을 주지 않는다.
중첩 트랜잭션은 부모 트랜잭션이 커밋될 때 같이 커밋된다. 부모 트랜잭션이 존재하지 않는다면 새로운 트랜잭션을 생성합니다.
@Transactional(propagation = Propagation.NEVER)
트랜잭션을 생성하지 않는다. 부모 트랜잭션이 존재한다면 예외를 발생시킨다.
@Transactional(propagation = Propagation.SUPPORT)
부모 트랜잭션이 있다면 합류하며 진행중인 부모 트랜잭션이 없다면 트랜잭션을 생성하지 않는다.
@Transactional(propagation = Propagation.NOT_SUPPORTED)
부모 트랜잭션이 있다면 보류시키며 부모 트랜잭션이 없다면 트랜잭션을 생성하지 않는다.
'Database' 카테고리의 다른 글
[Database] 인덱스 인덱싱에 관하여 (0) | 2023.08.06 |
---|---|
[Database] 정규화 과정 (0) | 2023.08.06 |
[Database] 동시성 제어 (1) | 2023.02.17 |
[Database] 회복 (0) | 2023.02.16 |
[Database] 정규화 (0) | 2023.02.13 |