java
Java Transaction
rockettttman
2020. 12. 1. 21:26
Java에서의 transaction 처리
트랜잭션이란?
- 쪼개질 수 없는 업무 처리의 단위
트랜잭션의 성질
- 원자성(Automicity) : 한 트랜잭션 내에서 실행한 작업은 하나로 간주한다. 즉 모두 성공 혹은 모두 실패다.
- 일관성(Consistency) : 트랜잭션은 일관성 있는 데이타베이스 상태를 유지한다. (data integrity 만족 등.)
- 격리성(Isolation) : 동시에 실행되는 트랜잭션들이 서로 영향을 미치지 않게 격리한다.
- 지속성(Durability) : 트랜잭션이 성공적으로 실행되면 결과는 항상 저장된다.
트랜잭션의 문제점
- Dirty Read
트랜잭션 A가 어떤 값을 1에서 2로 변경했다. 아직 커밋하지 않은 상황에서, 트랜잭션B가 같은 값을 읽는다면? 트랜잭션 B는 2를 읽을 것이다. 이 때 A가 롤백된다면? 트랜잭션B는 잘못된 값을 읽은 것이 된다. 요약하면, 아직 수정 중인 데이터에 접근을 허용할 경우 발생하는 데이터 불일치이다. - Non-Repeatable Read
트랜잭션 A가 값 1을 읽었다. 이후 A는 같은 쿼리를 또 실행할 예정인데, 그 사이에 트랜잭션 B가 값 1을 2로 바꾸고 커밋하면? A는 같은 쿼리 두번을 날렸으나, 두 쿼리의 결과가 다르게 된다. 요약하면, 한 트랜잭션에서 같은 쿼리를 두번 실행했을 때 발생하는 데이터 불일치이다 - Phantom Read
트랜잭션 A가 어떤 조건을 사용하여 특정 범위의 값들 [0,2,4,6,8]을 읽었다. 이후 A는 같은 쿼리를 실행할 예정인데, 그 사이에 트랜잭션 B가 같은 테이블에 값[1,3,5]을 추가하고 커밋했다면? A는 같은 쿼리를 다시 실행했지만 두번째 결과는 [1,2,3,4,5,6,8]을 얻는다. 요약하면, 한 트랜잭션에서 일정 범위의 레코드를 두번 이상 읽을 때 발생하는 데이터 불일치이다.
트랜잭션의 격리 수준
이와 같은 문제와 연관된 트랜잭션 격리 수준은 총 4가지이다. 수준(Level)이 높을수록 성능은 떨어지므로, 높은 격리 수준을 채택하는 것이 항상 좋은 방법은 아니다. 적절한 수준을 채택하는 것이 좋다.
- Level0 — Read Uncommited
다른 트랜잭션이 커밋하지 않은 레코드에 접근이 가능하다. 가장 낮은 수준의 트랜잭션 격리이다. 그래서 앞서 언급한 3가지 문제가 모두 발생한다. 그러나 성능은 가장 뛰어나다. - Level1 — Read Commited
항상 커밋된 레코드만 읽는다. 그러므로 아직 커밋되지 않는 레코드를 읽다가 발생하는 Dirty Read는 방지된다. 하지만 나머지 두가지 문제는 예방할 수 없다. 대부분의 DBMS에서 기본적으로 채택하고 있는 방식으로 알려져 있다. - Level2 — Repeatable Read
특정 트랜잭션이 읽은 레코드에 대하여 그 트랜잭션이 끝날 때까지 다른 트랜잭션이 수정/삭제하는 것을 방지한다. 한 트랜잭션에서 같은 쿼리를 두번 실행했을 때 결과가 다른 문제인 Non-Repeatable-Read가 예방된다. 하지만 특정 범위를 지정한 것도 아니고, 레코드 생성을 방지한 것은 아니므로 Phantom Read는 예방할 수 없다. Oracle에서 종종 사용하는 SELECT … FOR UPDATE 구문이 이 방식이다. - Level3 — Serializable Read
어떤 트랜잭션이 여러개의 레코드를 조회할 때, 그 트랜잭션이 끝날 때까지 조회 범위에 새로운 레코드를 조회/생성/수정/삭제 하는 것을 방지한다. 모든 동시성 관련 문제를 예방할 수 있으나, 성능이 가장 안좋다.
출처: https://preamtree.tistory.com/154 [Preamtree의 행복로그]
Spring에서의 Transaction 처리
@Transactional(propagation=Propagation.REQUIRED)
Transactional의 속성
전파레벨
- REQUIRED : 이미 시작된 트랜잭션(부모 트랜잭션)이 있으면 참여하고 없으면 새로 시작한다. (디폴트)
- SUPPORTS : 이미 시작된 트랜잭션이 있으면 참여하고 없으면 트랜잭션 없이 진행한다.
- REQUIRED_NEW : 부모 트랜잭션을 무시하고 항상 새로운 트랜잭션을 시작한다.
- NESTED : 메인 트랜잭션 내부에 중첩된 트랜잭션을 시작한다. 부모 트랜잭션 결과에는 영향을 받지만, 자신의 트랜잭션 결과는 부모에게 영향을 미치지 않는다.
- NOT_SUPPORTED : 트랜잭션을 사용하지 않게 한다. 이미 진행 중인 트랜잭션이 있으면 보류시킨다.
- MANDATORY : REQUIRED와 비슷하게 이미 시작된 트랜잭션이 있으면 참여하지만, 트랜잭션이 시작된 것이 없으면 예외를 발생시킨다. (혼자서 독립적으로 트랜잭션을 진행하면 안되는 경우에 사용)
- NEVER : 트랜잭션을 사용하지 않게 하며 이미 시작된 트랜잭션이 있으면 예외를 발생시킨다.
격리레벨
- DEFAULT : 기본 설정, 기본 격리 수준
- SERIALIZABLE : 가장 높은 격리, 성능 저하 가능성 있음
- READ_UNCOMMITED : 커밋되지 않은 데이터 읽을 수 있음
- READ_COMMITED : 커밋된 데이터에 대해 읽기 허용
- REPEATABLE_READ : 동일 필드에 대한 다중 접근 시 동일 결과 보장