Spring - transactional annotation

annotation 이해하기

Posted by Yan on May 27, 2024

Transactional

트랜잭션이란,

  • 데이터베이스의 데이터를 조작하는 작업의 단위(unit of work)
  • 데이터 베이스의 상태를 변경하는 작업, 또는 한번에 수행되어야 하는 연산들을 의미
  • begin, commit을 자동으로 수행해주고, 예외 발생시 rollback처리를 자동으로 수행해준다.

트랜잭션의 4가지 성질 ACID

  1. 원자성: 한 트랜잭션 내에서 실행한 작업들은 하나로 처리된다. 모두 성공, 혹은 모두 실패다.
  2. 일관성: 트랜잭션은 일관성 있는 데이터베이스 상태를 유지한다. 예를 들어, 데이터베이스에서 정한 무결성 제약 조건을 항상 만족해야 한다.
  3. 격리성: 동시에 실행되는 트랜잭션들이 서로 영향을 미치지 않도록 격리해야 한다. 예를 들어, 동시에 같은 데이터를 수정하지 못하도록 하는 것인데, 동시성과 관련된 성능 이슈로 인해 트랜잭션 격리 수준을 선택할 수 있다.
  4. 영속성: 트랜잭션을 성공저으로 마치면 항상 결과가 저장되어야 한다. 즁건애 시스템에 문제가 발생해도 데이터베이스 로그 등을 사용해서 성공한 트랜잭션 내용을 복구해야된다.

스프링에서의 트랜잭션 처리 방법

  • 어노테이션 @Transactional을 메소드, 클래스, 인터페이스에 추가하여 사용한다.
  • 적용된 범위에서는 트랜잭션 기능이 포함된 프록시 객체가 생성되어 자동으로 commit 혹은 rollback을 실행해준다.

@Transactional 옵션

No 옵션명 설명
1 isolation 트랜잭션에서 일관성 없는 데이터의 허용 수준 설정
- ex. isolation=Isolation.DEFAULT: 기본 격리 수준
2 propagation 트랜잭션 동작 도중 다른 트랜잭션을 호출 할 때, 어떻게 할 건지 지정하는 옵션
3 noRollbackFor 특정 예외 발생시 rollback하지 않는 설정
4 rollbackFor 특정 예외 발생시 rollback하는 설정
5 timeout 지정한 시간 내에 메소드 수행이 완료되지 않으면 rollback하는 설정. -1이면 timeout 사용x
6 readOnly 트랜잭션을 읽기 전용으로 설정

isolation

sql 스터디에서 봤던 trasction의 제어 정도와 비슷하다.

적용방법

1
2
3
@Transactional(isolation = READ_UNCOMMITED) {
  public void add() throw Dxception
}

트랜잭션 격리 정도를 조절하는 이유

격리 레벨이 높아질 수록 데이터 무결성 유지에 유리하지만, Locking으로 인해 동시성은 떨어질 수 있기 때문에 필요한 상황에 맞게 동시성과 데이터 무결성을 적절히 고려해야 한다.
Isolation 원칙을 덜 지키는 level을 사용할수록 문제가 발생할 가능성은 커지지만 동시에 더 높은 동시성을 얻을 수 있다

  1. DEFAUT
  2. READ_UNCOMMITED : 커밋되지 않은 데이터에 대한 읽기를 허용하기
    • Dirty Read 문제 발생 : 트랜잭션 1이 수정중인 데이터를 트랜잭션 2가 읽을 수 있다. 만약 드랜잭션 1의 작업이 정상 커밋되지 않아 롤백되면, 트랜잭션 2가 읽었던 데이터는 잘못된 데이터가 되어 데이터 정합성이 어긋나게 된다.
  3. READ_COMMITED : 커밋된 데이터 읽기 허용
    • Non-repeatable read 문제 발생 : 트랜잭션 1에서 조회중이었던 데이터가, 다른 트랜잭션에 의해 커밋되어 변화가 생기면, 수정된 데이터가 조회 되어 반복해서 같은 데이터를 읽어도 결과가 다르게 나오게 되어 반복조회 할 수 없다는 뜻이다.
    • Phantom read 문제 발생 : 트랜잭션 커밋 순서를 직렬화로 보장하는지. 먼저 커밋한 것을 정상 트랜잭션으로 간주하겠다는 뜻이다. -> Phantom read는 이를테면 첫 번째 세션에서 AGE BETWEEN 1 AND 15 조건으로 READ_COMMITED 인 데이터를 조회하고, 이때 데이터가 2개 있었는데, 두 번째 세션에서 AGE = 13인 데이터를 INSERT하고 COMMIT이 일어나는 거다. 그럼 첫 번째 세션에서 다시 조회했을 때, 데이터의 갯수는 3개가 되고, 유령 ROW가 생기는 것이다. 세션 1이 종료되지 않았다면 AGE BETWEEN 1 AND 15인 것은 LOCK이 걸려서 INSERT가 되지 않아야 첫 회 조회 시와 데이터 변동이 생기지 않았을 것이다.
  4. REPEATABLE_READ: 동일 필드에 대해 다중 접근시 모두 동일한 결과 보장. 반복해서 read operation을 수행하더라도 읽어 들이는 값이 변화하지 않는 정도의 isolation을 보장하는 level.
    • 처음 readSELECT operation 수행 시간을 기록한 뒤, 그 이후에 모든 read operation마다 해당 시점을 기준으로 consistent read를 수행한다. 첫 read의 snapshot을 보아서, 새로 commit된 데이터는 보이지 않는다.
    • gap lock을 활용해 조작을 가하려고 하는 row의 후보군을 다른 transaction이 건들지 못하도록 한다. (unique index와 unique search condition을 가진 쿼리에 대해서는 gap lock을 걸지 않는다)
    • REPEATABLE_READ`는 phantom read를 방지한다: 앞서 말한 phantom read 사례에서, INSERT를 시도할 때, gap lock이 걸려 있어서 세션 1이 lock을 해제할 때까지 기다린 후에 세션 2의 insert가 수행된다.

만약 SQL의 ISOLATION 설정과, spring boot의 transactional 옵션이 다르다면 어떤 것이 우선할까?

Spring Boot에서의 isolation level 설정은 database에 힌트를 주는 것이며, 실제 적용되는 것은 database와 JDBC driver의 설정이라고 한다.
Oracle은 READ_UNCOMMITED를 지원하지 않고, 만약 sprign boot에 READ_UNCOMMITED를 옵션으로 설정할 경우, 해당 옵션을 무시할 것 이다.
가장 높은 레벨의 격리 옵션이 SERIALIZABLE이다

SERIALIZABLE은 무엇인가?

  • SERIALIZABLE은 기본적으로 REPEATABLE READ와 동일하다.
  • 차이점은, AUTOCOMMIT이 꺼져있을 때, SELECT 쿼리가 전부 SELECT ... FOR SHARE로 자동 변경된다는 것이다.
  • 트랜잭션이 완료될 때까지 SELECT 문장이 사용하는 모든 데이터에 shared lock이 걸리므로 다른 사용자는 그 영역에 해당되는 데이터에 대한 수정 및 입력이 불가능gkek.

SELECT FOR SHARE는 무엇인가?

Shared Lock을 설정하는데 사용되는 것. 다른 트랜잭션이 동일한 행을 읽을 수는 있지만, 수정하려고 할 경우에는 차단된다.

SELECT * FROM table
WHERE condition
FOR SHARE;

오라클 잠금 메커니즘의 종류

  • Shared Lock S lock: 여러 트랜잭션이 동일한 자원을 공유할 수 있게 한다. SELECT ... FOR SHARE
  • Exclusive Lock X lock: 특정 자원에 대해 단일 트랜잭션만 접근할 수 있게 한다. 수정할 때 lock이 걸리는 것(SELECT ... FOR UPDATE), 일반적으로 DML 작업(예: INSERT, UPDATE, DELETE)이 수행될 때 설정된다.
  • 로우 레벨 잠금 (Row-Level Lock): 개별 행에 대해 잠금을 설정한다.
  • 테이블 잠금 (Table-Level Lock): 전체 테이블에 대해 잠금을 설정한다.
  • 데드락 (Deadlock) 감지 및 해제: 두 개 이상의 트랜잭션이 서로 교착 상태에 빠질 경우, Oracle은 이를 감지하고 해결한다.
S lock과 X lock 규칙
  • 여러 transaction이 동시에 한 row에 S lock을 걸 수 있다. 즉, 여러 transaction이 동시에 한 row를 읽을 수 있다.
  • S lock이 걸려있는 row에 다른 transaction이 X lock을 걸 수 없다. 즉, 다른 transaction이 읽고 있는 row를 수정하거나 삭제할 수 없다.
  • X lock이 걸려있는 row에는 다른 transaction이 S lock과 X lock 둘 다 걸 수 없다. 즉, 다른 transaction이 수정하거나 삭제하고 있는 row는 읽기, 수정, 삭제가 전부 불가능하다.

S lock은 중복으로 접근 가능하지만, X lock 발생시 어떤 쿼리도 접근 불가능하다.

propagation (전파속성)
  • 전파 속성은 하나의 트랜잭션에서 다른 트랜잭션으로 어떻게 전파되는지를 제어한다.
  • 스프링은 다양한 전파 속성을 제공하여 복잡한 트랜잭션 관리 상황에서 유연하게 대처할 수 있게 도와준다.

Propagation 옵션

  1. REQUIRED: 기본값. 현재 트랜잭션이 존재하면 참여하고, 없으면 새로운 트랜잭션을 시작한다.
  2. SUPPORTS: 현재 트랜잭션이 존재하면 참여하고, 없으면 트랜잭션 없이 진행한다.
  3. MANDATORY: 현재 트랜잭션이 존재해야 하며, 없으면 예외를 발생시킨다.
  4. REQUIRES_NEW: 항상 새 트랜잭션을 시작한다. 현재 트랜잭션이 있으면 잠시 보류하고 새 트랜잭션을 진행한다.
  5. NOT_SUPPORTED: 트랜잭션을 사용하지 않는다. 현재 트랜잭션이 있으면 보류한다.
  6. NEVER: 트랜잭션을 사용하지 않으며, 현재 트랜잭션이 있으면 예외를 발생시킨다.
  7. NESTED: 현재 트랜잭션 내에 중첩된 트랜잭션을 시작한다. 현재 트랜잭션 실패 시 중첩된 트랜잭션만 롤백된다.

전파 속성 사용 예제

1
2
3
4
@Transactional(propagation = Propagation.REQUIRED)
public void serviceMethod() {
    // 비즈니스 로직 구현
}

요약

스프링의 @Transactional은 트랜잭션의 관리를 간소화하며 다양한 설정을 제공하여 애플리케이션의 데이터 일관성과 무결성을 보장한다.

  • ACID 원칙을 지원하여 안정적인 데이터 관리가 가능하다.
  • 격리 수준(Isolation)전파 속성(Propagation) 설정을 통해 세밀한 트랜잭션 제어가 가능하다.
  • 특정 예외 발생 시 롤백 옵션을 통해 데이터를 보호할 수 있다.
  • 읽기 전용(ReadOnly) 설정으로 성능을 최적화할 수 있다.

스프링에서 트랜잭션을 효과적으로 관리함으로써, 애플리케이션의 복잡성을 관리하고, 시스템의 안정성을 높일 수 있다.

reference

Lock으로 이해하는 Transaction의 Isolation Level