[스프링] 트랜잭션 관리

트랜잭션 관리

트랜잭션 관리의 목적과 관리 방법은 다음과 같다.

  • 목적: 데이터의 일관성과 무결성 보장
  • 관리 방법 두 가지
    1. 프로그램 방식 트랜잭션 관리
    2. 선언적 트랜잭션 관리


트랜잭션 관리를 직접 프로그래밍하는 방식에서는 비즈니스 로직을 수행하는 메서드 내에 트랜잭션 관리 코드(트랜잭션 경계 설정)를 삽입하여 커밋과 롤백을 제어한다. AOP 관점에서 트랜잭션 관리는 일종의 공통 관심사이다. 선언적 트랜잭션 관리는 비즈니스 로직과 트랜잭션 관리 로직을 분리한다.

일련의 작업(쿼리 실행)을 수행하는 메서드 내에서 여러 쿼리 실행을 하나의 트랜잭션으로 실행할지 결정하여 쿼리 실행이 하나라도 실패하면 전체 쿼리 실행이 실패하도록 트랜잭션을 롤백하고 쿼리 실행이 모두 성공하면 트랜잭션을 커밋하도록 한다.


프로그램 방식 트랜잭션 관리

일련의 작업(쿼리 실행)을 수행하는 메서드 내에서 작업이 모두 성공적으로 종료되면 commit() 메서드 실행을 실행하여 트랜잭션을 커밋하고 작업이 하나라도 실패한 경우 rollback() 메서드를 실행하여 트랜잭션을 롤백한다.

JDBC는 기본적으로 하나의 쿼리 실행이 성공하면 곧 바로 트랜잭션을 커밋하므로 원하는 작업에 대해 트랜잭션 관리를 하기 위해 자동 커밋 기능을 설정 해제해야 한다. Connection 객체의 setAutoCommit(false) 메서드를 호출하여 쿼리 실행 완료 시 트랜잭션을 자동으로 커밋하지 않도록 설정할 수 있다.

하나의 데이터 소스에 대해서는 commit() 메서드와 rollback() 메서드로 트랜잭션 관리가 쉽게 가능하지만 여러 데이터 소스에 대한 트랜잭션을 관리하거나 자바 EE 애플리케이션 서버의 내장 트랜잭션 관리 기능을 사용하기 위해서는 JTA(Java Transaction API) 사용이 필요하며 ORM 프레임워크마다 서로 다른 JTA를 사용해야 할 수도 있다.

프로그램 방식의 트랜잭션 관리는 트랜잭션 관리 코드가 특정 데이터 접근 기술에 종속되므로 기술을 변경하려면 관련된 코드를 모두 변경해야 하는 의존성 문제가 있다. 스프링은 데이터 접근 기술에 독립적으로 간편하게 트랜잭션 관리 작업을 할 수 있도록 트랜잭션 관리자 인터페이스(PlatformTransactionManager), 트랜잭션 템플릿 인터페이스(TransactionTemplate), 트랜잭션 선언 기능 등 특정 기술에 의존하지 않는 유용한 트랜잭션 관리 기능을 지원한다.

트랜잭션 관리자는 새로운 트랜잭션의 시작, 트랜잭션 커밋 및 롤백을 통해 트랜잭션을 관리하는 역할을 수행한다. 트랜잭션 템플릿은 트랜잭션 관리자를 통해 트랜잭션을 직접 시작, 커밋, 롤백하는 코드를 반복적으로 작성할 필요 없이 트랜잭션을 적용할 코드 블록을 인자로 전달하여 호출만 하면 트랜잭션을 자동으로 관리해주는 메서드를 제공함으로써 트랜잭션 관리를 보다 간편하게 할 수 있게 도와준다. 트랜잭션 템플릿은 또한 트랜잭션을 적용할 코드의 실행 도중 언체크 예외가 발생하거나 코드 블록 내에서 TransactionStatussetRollbackOnly() 메서드를 명시적으로 호출하면 트랜잭션를 롤백한다.

스프링이 제공하는, 트랜잭션 관리 기능의 추상화를 위한 인터페이스를 사용하면 트랜잭션 관리를 편리하게 할 수 있고 트랜잭션 관리 코드를 특정 기술에 독립적으로 작성할 수 있다.


스프링 트랜잭션 관리 인터페이스 (PlatformTransactionManager)

PlatformTransactionManager은 스프링 트랜잭션 관리자를 포괄한 인터페이스이다. 데이터 접근 기술에 독립적인 트랜잭션 관리 기능을 캡슐화하여 트랜잭션 관리를 추상화하는 핵심 인터페이스이다. PlatformTransactionManager 인터페이스는 TransactionDefinition 인터페이스와 TransactionStatus 인터페이스를 사용해 트랜잭션을 생성하고 관리한다. PlatformTransactionManagersetDataSource() 메서드를 사용하여 트랜잭션을 관리할 데이터 소스를 지정할 수 있다.

스프링은 여러 데이터베이스 접근 구현 방식에 따라 적용 가능한 PlatformTransactionManager 인터페이스의 다양한 구현체들을 제공한다. JDBC로 하나의 데이터 소스에 접근하는 경우에는 DataSourceTransactionManager를 사용한다. 자바 EE 애플리케이션 서버에서 JTA로 트랜잭션을 관리할 경우, 서버에서 트랜잭션을 탐색하기 위해 JtaTransactionManager를 사용한다. 분산 트랜잭션(여러 리소스에 걸친 트랜잭션) 구현 시에도 JtaTransactionManager를 사용한다. ORM 프레임워크로 데이터베이스에 접근할 경우 HibernateTransactionManagerJpaTransactionManager 등의 해당 프레임워크 트랜잭션 관리자를 사용한다.

PlatformTransactionManager를 사용한 트랜잭션 관리 코드 작성은 다음 순서로 수행한다.

  1. 일련의 작업(쿼리 실행)을 수행하는 메서드가 정의된 클래스(JdbcDaoSupport 상속)에 PlatformTransactionManager 타입 속성 선언 및 DI를 위한 Setter 메서드 정의
  2. 새 트랜잭션 시작 전 TransactionDefinition 타입 트랜잭션 정의 객체 초기화
    • DefaultTransactionDefinition 구현체 사용
  3. 트랜잭션 정의 객체를 PlatformTransactionManager 객체의 getTransaction() 메서드의 인자로 전달하고 호출하여 트랜잭션 관리자에게 새 트랜잭션을 시작할 것을 요청
    • transactionManager.getTransaction(transactionDefinition)
  4. 쿼리가 모두 정상 실행되면 getTransaction() 메서드가 반환하는, 트랜잭션 상태 추적을 위한 TransactionStatus 객체(트랜잭션 상태)를 인자로 넘겨 트랜잭션 관리자에게 알림
    • transactionManager.commit(transactionStatus)
  5. 스프링 JDBC 템플릿에서 발생한 예외는 모두 DataAccessException 하위 타입이므로 해당 예외가 나면 트랜잭션 관리자가 트랜잭션을 롤백하도록 설정
    • transactionManager.rollback(transactionStatus)
  6. TransactionManager 구현체 빈 정의 시 적절한 트랜잭션 관리자 구현체 선택 및 데이터 소스 설정
    • transactionManager.setDataSource(dataSource())
  7. 트랜잭션 관리자를 PlatformTransactionManager로 선언하였으므로 빈 구성 클래스에서 JdbcDaoSupport 구현 객체 빈 정의 시 TransactionManager 구현체 빈을 JdbcDaoSupport 상속 클래스의 setTransactionManager() 메소드의 인자로 전달
    • JdbcDaoSupport상속객체.setTransactionManager(transactionManager())
    • setDataSource() 메소드의 인자로 데이터 소스 객체 전달도 필요하다.


트랜잭션 템플릿 (TransactionTemplate)

트랜잭션 관리자를 사용하여 트랜잭션을 직접 관리하는 코드를 구현하는 경우 트랜잭션 관리 코드가 중복된다. 스프링이 제공하는 트랜잭션 템플릿(TransactionTemplate)을 사용하면 전체 트랜잭션 관리 프로세스 및 예외 처리를 효과적으로 할 수 있다. 트랜잭션 관리자를 통해 트랜잭션을 직접 실행하고 커밋 및 롤백하는 반복적인 코드 작성이 필요하지 않다.

TransactionTemplate 객체 생성을 위해서는 TransactionManager가 필요하다. 트랜잭션 템플릿은 트랜잭션이 적용될 코드 블록을 캡슐화한 트랜잭션 콜백 객체를 실행한다.

TransactionCallback<T> 인터페이스를 구현한 콜백 클래스에서 코드 블록을 캡슐화한 뒤 TransactionTemplateexecute() 메서드에 전달한다. 트랜잭션 실행 결과 반환값이 있는 경우 콜백 객체의 반환값은 템플릿의 execute() 메서드가 반환한다. 트랜잭션 실행 결과 반환값이 필요 없는 경우 TransactionCallback<T> 인터페이스를 구현한 프레임워크 내장 객체인 TransactionCallbackWithoutResult 객체를 전달한다.

콜백 객체 실행이 정상적으로 종료되면 트랜잭션이 커밋되고, 콜백 객체를 실행하다가 언체크 예외(예: RuntimeException, DataAccessException)가 발생하거나, 명시적으로 doInTransactionWithoutResult() 메서드의 TransactionStatus 인자의 setRollbackOnly() 메서드를 호출하면 트랜잭션이 롤백된다.

TransactionTemplate을 사용한 트랜잭션 관리 코드 작성은 다음 순서로 수행한다.

  1. 일련의 작업(쿼리 실행)을 수행하는 메서드가 정의된 클래스(JdbcDaoSupport 상속)에 PlatformTransactionManager 타입 속성 선언 및 DI를 위한 Setter 메서드 정의
  2. 새 트랜잭션 시작 전 TransactionTemplate 트랜잭션 템플릿 객체 초기화
  3. TransactionCallback<T> 인터페이스를 구현한 콜백 클래스에서 코드 블록을 캡슐화한 뒤 TransactionTemplateexecute() 메서드의 인자로 전달
  4. 빈 구성 클래스에서 TransactionTemplate 객체 빈 정의 시 TransactionManager 빈을 TransactionTemplatesetTransactionManager() 메소드의 인자로 전달
    • transactionTemplate.setTransactionManager(transactionManager())
  5. 빈 구성 클래스에서 JdbcDaoSupport 구현 객체 빈 정의 시 TransactionTemplate 빈을 JdbcDaoSupport 상속 클래스의 setTransactionManager() 메소드의 인자로 전달
    • JdbcDaoSupport상속객체.setTransactionTemplate(transactionTemplate())
    • 트랜잭션 관리자 대신 트랜잭션 템플릿을 주입한다.
    • setDataSource() 메소드로 데이터 소스 객체도 전달 필요하다.


트랜잭션 템플릿 객체는 스레드-안전한 객체이므로 트랜잭션이 적용된 여러 빈에 걸쳐 사용될 수 있다.


선언적 트랜잭션 관리

트랜잭션을 적용할 메서드 또는 클래스에 @Transactional을 추가하여 선언적으로 트랜잭션을 관리할 수 있다. 이렇게 관리되는 트렌잭션을 스프링 관리(spring-managed) 또는 컨테이너 관리(container-managed) 관리 트랜잭션이라고도 한다. 메서드에 @Transactional만 붙이면 트랜잭션이 걸린 메서드로 선언된다. 클래스에 적용하면 해당 클래스의 모든 public 메서드에 트랜잭션이 적용된다. 인터페이스도 클래스 및 메서드 레벨에 @Transactional을 붙일 순 있지만 클래스 기반 프록시(예 : CGLIB 프록시)에서는 제대로 작동하지 않을 수 있으므로 권장하지 않는다.

선언적 트랜잭션 관리는 런타임에 적용되기 때문에 애플리케이션 코드는 트랜잭션 관리에 의존하지 않게 된다. 즉, 트랜잭션은 거래는 교차(횡단) 관심사가 된다.

@Transactional 어노테이션 기반 선언전 트랜잭션 관리를 위해 빈 구성 클래스에는 @EnableTransactionManegement을 붙여 트랜잭션 관리 활성화 설정을 해야 한다. IoC 컨테이너에 선언된 빈들을 찾아 @Transactional을 붙인 메서드(또는 이 애너테이션을 붙인 클래스의 모든 메서드) 중 public 메서드를 가져와 어드바이스를 적용한다.

어노테이션을 사용한 선언적 트랜잭션 관리의 경우 스프링 AOP가 프록시 기반으로 작동하는 한계(IoC 컨테이너에 선언된 빈의 public 메서드에만 어드바이스를 적용할 수 있디) 때문에 public 메서드에만 적용 가능하다. 따라서 public 메서드 스코프로 트랜잭션 관리가 국한되는 한계를 해결하도 public 외의 메서드나 IoC 컨테이너 외부에서 만든 객체의 메서드에 대해서도 트랜잭션을 관리해야 할 경우에는 AnnotationTransactionAspect라는 AspectJ 애스팩트를 사용해야 한다.


트랜잭션 속성 설정

트랜잭션은 전파(propagation), 격리 수준(isolation level), 타임아웃, 롤백 규칙, 읽기 전용(read-only) 트랜잭션 여부 등의 속성을 설정할 수 있다. 스프링은 트랜잭션의 모든 관련 설정을 TransactionDefinition 인터페이스로 캡슐화한다. TransactionDefinition 인터페이스는 트랜잭션을 관리하는 인터페이스인 PlatformTransactionManager에서 사용된다. PlatformTransactionManager의 핵심 메서드인 getTransaction()TransactionDefinition 인터페이스를 인자로 전달받고 TransactionStatus 인터페이스를 반환한다. TransactionStatus 인터페이스는 트랜잭션 실행을 제어하기 위해 트랜잭션의 상태 정보를 확인하기 위해 사용된다. 이 인터페이스를 통해 트랜잭션 결과를 설정하고 트랜잭션의 완료 여부, 새 트랜잭션인지의 여부를 확인할 수 있다.


트랜잭션 전파 속성 설정

트랜잭션이 적용된 메서드(작업 메서드)를 다른 메서드(요청 메서드)가 호출하면 트랜잭션 전파가 일어난다. 호출한 메서드 역시 기존 트랜잭션 내에서 실행할지, 아니면 트랜잭션을 하나 더 생성해 자신만의 고유한 트랜잭션에서 실행할지 결정이 필요하다. 기존 트랜잭션을 그대로 적용하는 경우(요청 메서드에서 작업 메서드로 트랜잭션이 전달되는 경우) 작업 메서드에서 쿼리 실행이 실패하면 요청 메서드의 트랜잭션이 롤백된다. 기존 트랜잭션을 적용하지 않고 새로운 트랜잭션을 시작하는 경우 작업 메서드에서 퀴리 실행이 실패해도 요청 메서드의 트랜잭션이 롤백되지 않는다.

트랜잭션 전파 방식은 @Transactionalpropagation 트랜잭션 속성에 명시한다.

@Transactional(propagation = Propagation.전파속성)

스프링 org.springframework.transaction.TransactionDefinition 인터페이스에 일곱 가지 트랜잭션 전파 방식이 정의되어 있다.

  • REQUIRED (기본 전파 방식): 진행 중인 트랜잭션이 있으면 현재 메서드를 그 트랜잭션에서 실행하되, 그렇지 않을 경우 새 트랜잭션을 시작해서 실행한다.
  • REQUIRES_NEW: 항상 새 트랜잭션을 시작하여 현재 메서드를 실행하고 진행 중인 트랜잭션이 있으면 잠시 중단시킨다.
  • SUPPORTS: 진행 중인 트랜잭션이 있으면 현재 메서드를 그 트랜잭션 내에서 실행하되, 그렇지 않을 경우 트랜잭션 없이 실행한다.
  • NOT_SUPPORTED: 트랜잭션 없이 현재 메서드를 실행하고 진행 중인 트랜잭션이 있으면 잠시 중단시킨다.
  • MANDATORY: 반드시 트랜잭션을 적용하고 현재 메서드를 실행하되 진행 중인 트랜잭션이 없으면 예외를 던진다.
  • NEVER: 반드시 트랜잭션 없이 현재 메서드를 실행하되 진행 중인 트랜잭션이 있으면 예외를 던진다.
  • NESTED: 진행 중인 트랜잭션이 있으면 현재 메서드를 이 트랜잭션의 (JDBC 3.0 세이브포인트(savepoint) 기능이 있어야 가능한) 중첩 트랜잭션(nested transaction) 내에서 실행한다.
    • 진행 중인 트랜잭션이 없으면 새 트랜잭션을 시작해서 실행한다.
    • 다른 속성들은 자바 EE 트랜잭션 전달 방식과 유사한 반면 이 방식은 스프링에서만 가능하다.
    • 장시간 실행되는 업무를 처리하면서 배치 실행 도중 끊어서 커밋하는 경우 유용하다.
    • 중간에 작업이 잘못되어도 중첩 트랜잭션을 롤백하면 끊어서 실행한 분량의 작업만 소실된다.


모든 트랜잭션 관리자 구현체가 이들 전파 방식을 전부 지원하는 건 아니며 하부 리소스에 따라 달라질 수도 있다. 트랜잭션 관리자가 다양한 전파 방식을 지원한다 해도 데이터베이스가 지원하는 격리 수준에 따라 영향을 받을 수밖에 없다.


트랜잭션 격리 속성 설정

서로 다른 트랜잭션이 동일한 데이터에 접근하는 경우 여러 문제가 발생할 수 있으므로 트랜잭션들은 서로 격리되어야 한다. 하나의 트랜잭션에서 일련의 쿼리를 실행하고 있는 도중에 다른 트랜잭션이 데이터를 수정하는 경우 동일한 쿼리 실행 결과가 서로 다르게 나타나는 문제(데이터의 일관성이 낮아지는 문제)가 발생하게 된다.

트랜잭션 격리 수준은 데이터의 일관성과 작업 처리의 동시성 수준의 트레이드오프라고 볼 수 있다. 격리 수준이 낮으면 많은 작업을 동시에 처리할 수 있지만 데이터 일관성이 낮아진다. 격리 수준이 높으면 데이터 일관성은 높아지지만 작업을 동시에 처리할 수 있는 수준이 낮아진다. 따라서 애플리케이션이 데이터 일관성과 작업 처리의 동시성 중 어떤 부분을 더 우선순위에 두는지에 따라 격리 수준을 조절해야 한다.

트랜잭션 격리가 일어나지 않는 경우 발생할 수 있는 문제는 크게 네 가지이며 트랜잭션의 격리는 그 수준에 따라 네 가지 문제 중 일부를 막을 수 있게 해준다.

트랜잭션 격리가 일어나지 않는 경우 발생할 수 있는 네 가지 문제는 다음과 같다.

  1. 오염된 값 읽기 (Dirty Read): 트랜잭션1이 값을 수정한 상태이고 커밋을 하기 전 상태에서 트랜잭션2가 해당 값을 읽은 경우 트랜잭션1이 롤백되면 트랜잭션2가 읽은 값은 일시적인 값으로 더 이상 유효하지 않다.
  2. 재현 불가능한 읽기 (Nonrepeatable Read): 트랜잭션1이 값을 읽고 난 후 트랜잭션2가 그 값을 수정하고 커밋을 한 경우 트랜잭션1이 동일한 값을 다시 읽으면 이전의 값과는 다른 값을 얻게 된다.
  3. 허상 읽기 (Phantom Read): 트랜잭션1이 테이블의 로우를 몇 개 읽은 후 트랜잭션2가 동일한 테이블에 새 로우를 삽입하고 커밋한 경우 이후에 트랜잭션1이 동일한 테이블을 다시 읽으면 트랜잭션2가 삽입한 로우가 보이게 된다.
  4. 소실된 수정 (Lost Updates): 트랜잭션1과 트랜잭션2가 수정을 위해 동일한 로우를 읽고 수정하려는 경우 트랜잭션1이 먼저 로우를 수정하고 커밋을 하기 전에 트랜잭션2가 동일한 로우를 수정하였고 트랜잭션1이 커밋한 후에 트랜잭션2도 커밋을 하게 되면 트랜잭션1이 수정한 로우를 트랜잭션2가 덮어쓰게 되어 트랜잭션1이 수정한 내용이 소실된다.


이러한 문제를 해결하기 위해 트랜잭션을 격리해야 하며 격리 수준에 따라 데이터의 일관성이 달라지게 된다. 격리 수준이 높을수록 데이터의 일관성이 높아진다.

트랜잭션 격리 수준은 다음과 같다(낮은 순서대로).

  1. 커밋되지 않은 데이터 읽기 (Read Uncommitted): 다른 트랜잭션이 아직 커밋하지 않은(uncommitted) 값을 한 트랜잭션이 읽을 수 있다.
    • 오염된 값 읽기, 재현 불가한 읽기, 허상 읽기 문제가 발생 가능하다.
  2. 커밋된 데이터 읽기 (Read Committed): 한 트랜잭션이 다른 트랜잭션이 커밋한(committed) 값만 읽을 수 있다.
    • 오염된 값 읽기 문제를 해결할 수 있다.
    • 재현 불가한 읽기, 허상 읽기 문제는 발생 가능하다.
  3. 재현 가능한 읽기 (Repeatable Read): 트랜잭션이 테이블의 로우를 여러 번 읽어도 동일한 로우를 읽도록 보장하며 트랜잭션이 지속되는 동안에는 다른 트랜잭션이 해당 테이블에 삽입은 가능하지만 수정, 삭제를 할 수 없다.
    • 오염된 값 읽기, 재현 불가한 읽기 문제를 해결할 수 있다.
    • 허상 읽기 문제는 발생 가능하다.
  4. 직렬화 가능한 읽기 (Serializable Read): 트랜잭션이 테이블을 여러 번 읽어도 정확히 동일한 로우를 읽도록 보장하며 트랜잭션이 지속되는 동안에는 다른 트랜잭션이 해당 테이블에 삽입, 수정, 삭제를 할 수 없다.
    • 이 격리 수준은 동시성 문제를 모두 해결하지만 성능이 현저히 떨어진다.


재현 가능한 읽기와 직렬화 가능한 읽기 격리 수준의 차이는 트랜잭션이 지속되는 도중 삽입을 할 수 있는 그렇지 않은지이다.

트랜잭션 격리 수준은 @Transactionalisolation 트랜잭션 속성에 다음과 같이 명시한다.

@Transactional(isolation = Isolation.격리속성)

스프링 org.springframework.transaction.TransactionDefinition 인터페이스에 다섯 가지 격리 수준이 정의되어 있다.

  1. DEFAULT: 데이터베이스의 기본 격리 수준을 사용한다.
    • 대다수 데이터베이스의 기본 격리 수준은 커밋된 데이터 읽기이다.
  2. READ_UNCOMMITTED
  3. READ_COMMITTED
  4. REPEATABLE_READ
  5. SERIALIZABLE


동시 실행 제어

트랜잭션 격리가 설정됨에 따라 하나의 작업에 의해 트랜잭션이 실행 중일 때 다른 작업에 의한 트랜잭션은 동일한 데이터에 접근(조회, 수정, 삭제)이 불가능할 수 있으며 이를 데이터 경합(data race)이라고 한다. 데이터 경합으로 인해 실패한 트랜잭션은 그대로 실패한 상태로 종료하여 에러 메시지를 반환하거나 재시도될 수 있다. 데이터 경합을 막기 위해서는 데이터베이스 잠금(lock)(또는 데이터 잠금) 및 적절한 트랜잭션 격리 수준 설정이 필요하다.

트랜잭션 격리 수준은 동시 실행 작업을 제어하기 위한 설정이다. 동시 실행 작업 도중 발생하는 데이터 경합에 의해 실패한 트랜잭션을 그대로 종료하거나 재시도하는 방법(데이터 경합을 해결하는 방법)에는 크게 두 가지가 있다. 애플리케이션은 이 두 가지 방법 중 하나를 선택하여 데이터 경합을 해결한다.

  1. 낙관적 동시 실행 제어(optimisic concurrency control) 또는 낙관적 잠금(optimistic lock): 데이터 변경 사항이 커밋되었을 때에만 데이터를 잠근다.
  2. 비관적 동시 실행 제어(perssimistic concurrency control) 또는 비관적 잠금(perssimistic lock): 데이터가 변경 중일 때에도 데이터를 잠근다.


낙관적 동시 실행 제어는 데이터 경합이 발생할 가능성이 낮거나 데이터 잠금으로 인한 리소스 오버헤드가 높아 데이터 잠금을 사용하지 않는 것이 효율적이라는 가정을 기반으로 한다. 낙관적 동시 실행 제어는 데이터가 변경되는 도중에 데이터 잠금을 사용하지 않으며 데이터를 변경하는 트랜잭션이 실행 중이더라도 다른 트랜잭션이 데이터를 변경할 수 있도록 한다. 데이터를 변경하는 트랜잭션 중 하나의 트랜잭션이 변경을 완료하고 커밋하게 되면 다른 트랜잭션은 변경한 데이터를 커밋할 수 없으며 커밋을 시도하는 순간 데이터 충돌이 발생하게 된다. 서로 다른 트랜잭션은 서로의 데이터 변경 사항에 대해 알지 못한 채 자신만의 데이터 변경 작업을 수행하며 커밋을 가장 먼저한 트랜잭션의 데이터 변경 사항이 반영된다. 하나의 데이터를 동시에 변경하는 경우가 많지 않은 경우(데이터 변경이 매우 긴 시간 간격으로 이루어지는 상황) 낙관적 동시 실행 제어가 유용하다.

비관적 동시 실행 제어는 데이터 경합이 발생할 가능성이 높거나 데이터 잠금으로 인한 리소스 오버헤드를 충분히 감당할 수 있는 환경에서 데이터 잠금을 사용하는 것이 효율적이라는 가정을 기반으로 한다. 비관적 동시 실행 제어는 데이터가 변경되는 도중에도 데이터 잠금을 사용하며 데이터를 변경하는 트랜잭션이 실행 중이면 다른 트랜잭션은 데이터를 변경할 수 없도록 한다. 데이터를 변경하는 트랜잭션이 실행 중이면 다른 트랜잭션은 데이터를 변경 조차할 수 없으며 변경을 시도하는 순간 데이터 충돌이 발생하게 된다. 신규 데이터 변경 작업이 기존 데이터 변경 작업이 완료될 때까지 기다릴 수 있는 경우(데이터 변경 및 완료 작업이 매우 짧은 시간 간격으로 이루어지는 상황) 비관적 동시 실행 제어가 유용하다.


트랜잭션 롤백 속성 설정


참고

Comments