트랜잭션
데이터베이스의 상태를 변화시키는 수행 작업의 단위
동시성 문제에 대해 트랜잭션을 사용하면 해결된다고 생각할 수 있지만 사실 그렇지 않다.
트랜잭션은 길수록 오버헤드가 발생하기 때문이다.
jpa respository에서 제공하는 연산은 트랜잭션이 적용된다.
트랜잭션을 계속 걸면 성능과 트레이드 오프가 생기게 된다.
우리는 어느정도의 수준으로 트랜잭션을 걸지 정해야한다.
트랜잭션을 어느 범위 까지 걸 것인지 결정하는 것은 전파전략이라고한다.
Spring은 @Transactional (선언적 트랜잭션)을 이용해 여러 트랜잭션을 묶어 하나의 큰 트랜잭션 경계를 만들 수 있다.
격리수준
- Read Uncommitted
- Read Committed
- Repeatable Read
- Serializable
Read Uncommitted
가장 하위 레벨의 격리 수준으로 트랜잭션이 commit 되지 않은 데이터를 다른 트랜잭션이 읽을 수 있도록 허용한다.
Read Committed
트랜잭션의 커밋이 확정된 데이터만 읽을 수 있게 허용한다.
Repeatable Read
트랜잭션이 시작되기 전에 커밋된 내용에 대해서만 읽을 수 있도록 허용된다. 트랜잭션 도중 다른 트랜잭션이 커밋 되어도, 새로 커밋된 데이터는 보이지 않게 된다.
Serializable
가장 높은 수준의 격리 수준으로 특정 트랜잭션이 수행중엔 해당 테이블에 다른 트랜잭션이 UPDATE, DELETE, INSERT를 못하게 막는다. 동시성이 가장 낮은 격리 수준이기 때문에 거의 사용하지 않는다고 한다.
"아래로 내려갈수록 트랜잭션간 고립 정도가 높아지며, 성능이 떨어지는 것이 일반적이다.
일반적인 온라인 서비스에서는 READ COMMITTED나 REPEATABLE READ 중 하나를 사용한다.
따로 설정하지 않는다면 디폴트로 설정되는 듯 하다 : (oracle = READ COMMITTED, mysql = REPEATABLE READ)"
사용예시
@Transactional(isolation=Isolation.SERIALIZABLE, propagation=Propagation.REQUIRES_NEW)
트랜잭션은 논리적으로 5가지의 상태에 있을 수 있다.
- Active
- 트랜잭션이 현재 실행 중인 상태
- Failed
- 트랜잭이 실행되다 오류가 발생해서 중단된 상태
- Aborted
- 브랜잭션이 비정상 종료되어 Rollback 이 수행된 상태
- Partially Committed
- 트랜잭션의 연산이 마지막까지 실행되고 Commit이 되기 직전 상태
- Committed
- 트랜잭션이 성공적으로 종료되어 Commit 연산을 실행한 후의 상태
몇가지 전파레벨에 대해 알아보자 (여기 설명한 것보다 더 많은 전파레벨이 존재한다)
@Transactional(propagation = Propagation.REQUIRED)
public void doSomething() { ... }
특정 메소드의 트랜잭션이 Propagation.REQUIRED로 설정되었을 때의 트랜잭션 동작은 다음과 같다. 기본적으로 해당 메소드를 호출한 곳에서 별도의 트랜잭션이 설정되어 있지 않았다면 트랜잭션를 새로 시작한다. 만약, 호출한 곳에서 이미 트랜잭션이 설정되어 있다면 기존의 트랜잭션 내에서 로직을 실행한다. (동일한 연결 안에서 실행된다.) 예외가 발생하면 롤백이 되고 호출한 곳에도 롤백이 전파된다. 이러한 Propagation.REQUIRED 동작 방식을 원할 경우 기본값으로 설정되어 있기 때문에 생략해도 된다.
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void doSomething() { ... }
Propagation.REQUIRES_NEW로 설정되었을 때에는 매번 새로운 트랜잭션을 시작한다. (새로운 연결을 생성하고 실행한다.) 만약, 호출한 곳에서 이미 트랜잭션이 설정되어 있다면(기존의 연결이 존재한다면) 기존의 트랜잭션은 메써드가 종료할 때까지 잠시 대기 상태로 두고 자신의 트랜잭션을 실행한다. 새로운 트랜잭션 안에서 예외가 발생해도 호출한 곳에는 롤백이 전파되지 않는다. 2개의 트랜잭션은 완전히 독립적인 별개로 단위로 작동한다.
@Transactional(propagation = Propagation.NESTED)
public void doSomething() { ... }
Propagation.NESTED는 기본적으로 앞서 설명한 Propagation.REQUIRED와 동일하게 작동한다. 중요한 차이점은, SAVEPOINT를 지정한 시점까지 부분 롤백이 가능하다는 것이다. 유의할 점은, 데이터베이스가 SAVEPOINT 기능을 지원해야 사용이 가능하다.
아직까지 프로젝트에서 트랜잭션에 대해 깊게 생각하고 적용해 본 적은 없는데 조금더 공부 한 후 적용해 보도록 해야겠다.
트랜잭션 정합성내용 추가 필요(lock)
출처
https://okky.kr/articles/1151991
https://developyo.tistory.com/250