자바 & 스프링

트랜잭션

p829911 2021. 12. 20. 19:00

트랜잭션?

  • 여러 쿼리를 논리적으로 하나의 작업으로 묶어주는 것

계좌 이체를 예로 들어보자, A 계좌에서 B 계좌로 이체를 하려고 할 때 A 계좌에서 만원을 출금하고 B 계좌에 입금하기 전에 에러가 났을 때의 상황을 어떻게 처리할 것인가? 위의 계좌이체 작업은 두 가지 작업으로 이뤄진다.

  • A 계좌에서 만원을 출금하고, DB에 반영
  • B 계좌에 만원을 입금하고, DB에 반영

위 두 가지 작업이 모두 정상적으로 처리되어야 계좌이체 작업이 완료되었다고 할 수 있다.
다시 말해서 하나의 작업만 완료되고 하나의 작업이 실패하면 계좌 이체 작업에 논리적으로 오류가 있다.
두 작업 모두 성공하거나, 아무일도 일어나지 않아야 한다. (A 계좌에서 만원을 출금한 일도 일어나지 않아야 한다)
이렇게 작업이 모두 성공할 때만 실제 DB에 반영하고, 하나라도 실패하면 아무일도 일어나지 않게 여러 작업을 논리적으로 묶어 주는 것을 트랜잭션이라고 한다.

트랜잭션의 성질: ACID

  • 트랜잭션이 안전하게 수행된다는 것을 보장하기 위한 성질
  • Atomicity (원자성): 트랜잭션은 DB에 모두 반영되거나, 전혀 반영되지 않아야 한다.
  • Consistency (일관성): 트랜잭션 작업처리 결과는 항상 일관성 있어야 한다.
    데이터베이스는 항상 일관된 상태로 유지되어야 한다. DB에 여러 제약 조건에 맞는 상태를 보장해 준다.
    계좌 잔액이 마이너스가 되면 안된다는 DB의 조건이 있을 때 이 조건이 위배될때 트랜잭션은 바로 종료된다.
  • Isolation (독립성): 둘 이상의 트랜잭션이 동시 실행되고 있을 때, 어떤 트랜잭션도 다른 트랜잭션 연산에 끼어들 수 없다.
    각각의 트랜잭션은 서로 간섭 없이 독립적으로 이루어져야 한다.
    A의 계좌에서 돈이 빠져나가고, B의 계좌에 아직 돈이 들어가지 않은 상태를 다른 트랜잭션이 조회할 수 없어야 한다.
  • Durability (지속성): 트랜잭션이 성공적으로 완료되었으면 결과는 영구히 반영되어야 한다.
    한번 송금이 성공하면 은행시스템에 문제가 생기더라도 송금이 성공한 상태로 복구할 수 있어야 한다.

트랜잭션 격리 수준: 동시성을 위해 독립성 조건을 완화한다.

  • 동시에 DB에 접근할 때 그 접근을 어떻게 제어할지에 대한 설정

밑으로 갈수록 격리 수준이 높아지지만, 성능이 떨어진다.

  • READ-UNCOMMITTED: 커밋 전의 트랜잭션의 데이터 변경 내용을 다른 트랜잭션이 읽는 것 허용
  • READ-COMMITTED: 커밋이 완료된 트랜잭션의 변경사항만 다른 트랜잭션에서 조회 가능
  • REPEATABLE-READ: 트랜잭션 범위 내에서 조회한 내용이 항상 동일함을 보장
  • SERIALIZABLE: 한 트랜잭션에서 사용하는 데이터를 다른 트랜잭션에서 접근 불가

각 격리 수준에서 나타날 수 있는 문제점

  • Dirty Read: 트랜잭션 처리된 작업의 중간 결과를 볼 수 있는 현상을 말한다, 즉 commit 되지 않은 정보를 볼 수 있는 현상을 말하는 것
  • Non-Repeatable Read: 한 트랜잭션 안에서 같은 쿼리를 두 번 실행했을 때, 다른 값이 나오는 Read 현상을 말한다.
    어떤 트랜잭션이 commit 하기 전과 후의 쿼리 결과가 다르다.
  • Phantom Read: 한 트랜잭션 안에서 첫번째 쿼리 수행 결과와 두번째 쿼리 수행 결과가 다른 것을 의미한다.
    - 외부에 동시에 실행 중인 트랜잭션의 Insert 작업에 의해 발생하는 Read 현상. 즉, 결과 범위에 속하지 않은 레코드가 외부 작업에 의해 있을 수도 있고, 없을 수도 있다는 것을 뜻한다.
    - 다른 트랜잭션에서 insert 를 했거나 delete를 했을 때 자신의 트랜잭션에서 조회한 자료 세트 범위가 다른 트랜잭션에 의해서 변경 될 경우 그것이 자신이 조작하지 않았음에도 불구하고, 유령처럼 바뀌는 것

트랜잭션 전파 타입

트랜잭션 전파 속성은 하나의 요청에 여러 트랜잭션이 발생할 때, 트랜잭션 경계를 어떻게 설정할 지를 결정하는 속성이다. 설정된 트랜잭션 전파 속성과 기존에 진행 중인 트랜잭션의 상태를 고려해야하는데, 이 과정에서 트랜잭션 동기화를 사용한다.

 

트랜잭션 동기화

JDBC를 이용할 때 직접 여러 개의 작업을 하나의 트랜잭션으로 관리하려면 Connection 객체를 공유하는 등 상당히 불필요한 작업이 많이 생긴다. Spring은 이러한 문제를 해결하기 위해 트랜잭션 동기화(Transaction Synchronization) 기술을 제공하고 있다. 트랜잭션 동기화는 트랜잭션을 시작하기 위한 Connection 객체를 특별한 저장소에 보관해두고 필요할 때 꺼내 쓸 수 있도록 하는 기술이다. 트랜잭션 동기화 저장소는 작업 쓰레드마다 Connection 객체를 독립적으로 관리하기 때문에 멀티쓰레드 환경에서도 충돌이 발생할 여지가 없다.

// 동기화 시작
TransactionSynchronizeManager.initSynchronization();
Connection c = DataSourceUtils.getConnection(dataSource);

// 작업 진행

// 동기화 종료
DataSourceUtils.releaseConnection(c, dataSource);
TransactionSynchronizeManager.unbindResource(dataSource);
TransactionSynchronizeManager.clearSynchronization();