[SpringFW] 트랜잭션 간단정리





서론

스프링을 사용하는 자바 개발자가 알아야 할 트랜잭션에 대한 간단한 개념과 주의사항에 대해 포스팅합니다.




1. 트랜잭션이란 무엇인가?

  • 트랜잭션이란 데이터베이스의 상태를 변화시키기 위해 수행하는 작업의 단위입니다. 여러 쿼리문이 있더라도 commit이나 rollback이 수행되어야지만 트랜잭션이 종료되었다고 볼 수 있습니다.

  • 주로 자바 프로젝트에서는 DB에 엑세스하는 비즈니스 로직에 트랜잭션에 대한 처리를 하거나 혹은 AOP를 통해서 공통 트랜잭션 처리를 할 것입니다.

  • 트랜잭션 처리가 중요한 이유는 DB에 데이터를 엑세스하는 도중 어떠한 문제가 발생했을 경우(애플리케이션 서버 문제, 네트워크, DB제약조건 등) 비즈니스 로직과 DB의 상태를 안전하게 보장하기 위해서입니다. 물론 그 외의 다른 이유들도 있지만 주된 이유는 안정성유지 및 정상상태로의 회복을 위해서입니다.




2. 트랜잭션의 특징

트랜잭션의 특징에는 흔히들 알고 있는 ACID 네가지 특징이 있습니다.

  1. A (Atomicity, 원자성) - 트랜잭션 안의 작업들은 모두 성공하거나 모두 실패해야 합니다. All or Nothing.

  1. C (Consistency, 일관성) - 모든 트랜잭션은 일관성 있는 DB 상태를 유지해야 합니다.(DB의 무결성 제약조건을 항상 만족해야함)

  1. I (Isolation, 격리성) - 한 트랜잭션이 작업 도중 다른 트랜잭션으로 인해 영향받지 않아야 한다. 성능에 가장 중요한 요소인 격리성은 네가지 특성중 가장 유연하게 설정 가능합니다. 동시성 이슈에 따라 격리성 레벨을 조절해야 합니다.

  1. D (Durability, 지속성) - 트랜잭션이 성공적으로 끝나든, 실패하든 그 결과는 DB에 영원히 지속되어야 합니다. (로그)




3. 스프링에서의 트랜잭션

개발자가 비즈니스 로직에만 집중할 수 있도록 스프링은 크게 2가지 트랜잭션 기술을 지원해줍니다.

  • 선언적방식
  • 프로그래밍 방식

3-1. 선언적 트랜잭션

바로 @Transactional 어노테이션을 이용한 트랜잭션 방식입니다. 선언적방식은 Spring AOP를 이용한 방식입니다.

  • 선언적방식은 유지보수가 쉽습니다.
  • 다수의 트랜잭션을 관리해야할 때 선호하는 방식입니다.
  • 프록시 방식이므로, 내부 메서드(private)에서는 사용이 불가능합니다.
  • 클래스, 메서드 둘 다 어노테이션을 달 수 있겠지만, 더 좁은 범위인 메서드에 다는 것이 유리합니다.

3-2. 프로그래밍 트랜잭션

  • TransactionTemplate을 구현한 PlatformTransactionManager 구현체를 사용합니다.
  • 해당 구현체에는 getTransaction(), commit(), rollback() 세 가지 메소드가 있습니다.
  • 데이터에 엑세스하기 위해서는 JPA 뿐만 아니라 R2DBC, JdbcTemplate 등 여러가지 방법이 존재하고, 각각 트랜잭션 관리 방법이 다르기 때문에 스프링에서는 트랜잭션 관리를 다양하게 하기 위한 이와 같은 추상화 개념을 지원합니다.




4. 트랜잭션 경쟁

트랜잭션의 특징 중 하나인 격리레벨에 따라 트랜잭션 경쟁 문제는 복잡해집니다.
참고로 흔히들 사용하는 mysql의 기본 격리 레벨은 REPEATABLE READ(Level 2) 입니다.
데이터베이스의 격리레벨은 다음과 같습니다.



  1. READ UNCOMMITED : 트랜잭션 실행 도중 커밋되지 않은 데이터를 읽을 수 있습니다.
    더티 리드(Dirty Read) : 다른 트랜잭션이 아직 커밋하지 않은 데이터를 읽을 수 있습니다.


  2. READ COMMITED : 트랜잭션 실행 도중 읽는 데이터들은 모두 커밋된 데이터들입니다.
    UNREPEATABLE READ : 다른 트랜잭션에 의해 커밋된 데이터를 읽게 되어 한 트랜잭션 내에서 SELECT를 두번 했을 경우 다른 값이 나올 수 있습니다.


  3. REPEATABLE READ : 트랜잭션 실행 후 읽는 모든 데이터는 같은 값을 보장합니다. (mysql Default level)
    트랜잭션 실행동안 읽는 모든 데이터에 Shared Lock을 걸어서 읽기는 가능하지만 수정은 불가능하게 만듭니다.
    팬텀 리드(PHANTOM READ) : 일정 범위의 레코드를 읽었을 때 다른 결과값이 나올 수 있습니다. (트랜잭션이 끝나지 않았는데도 원래는 없던 유령값이 존재할 수 있음.)


  4. SERIALIZABLE LEVEL : 트랜잭션 종료시까지 모든 데이터에 락을 걸어 최고 수준의 정합성을 보장합니다.
    팬텀리드 방지.


당연히 SERIALIZABLE LEVEL을 사용해야 할 것 같지만, 성능문제로 주로 READ COMMITED, REPEATABLE READ 격리레벨을 주로 사용합니다. 4번으로 갈수록 데이터 정합성은 높지만, 성능이 떨어집니다.





5. 트랜잭션 주의할 점

  1. 트랜잭션은 가능한 한 짧아야 합니다. -> Locking 시간을 짧게 가져가자.

    1. 트랜잭션이 길어지게 되는 상황에서는 다른 트랜잭션에 의해 데드락 혹은 점유시간이 길어지게 되어 네트워크 지연 문제가 발생할 수 있습니다.
    2. 트랜잭션이 걸린 로직안에 DB 엑세스 로직 뿐만 아니라 예를 들어, S3 업로드 라던지 이미지 변환 같은 작업이 들어가게되면 트랜잭션 시간이 길어지게 됩니다. 이는 예기치 못한 장애로 이어질 가능성이 높습니다.

  2. CheckedException이 발생하는 경우에는 트랜잭션이 롤백되지 않습니다. UnchckedException은 예상치 못한 오류라 롤백시키는 것이 Default 전략입니다.(라고 우아한 형제들에서 누군가가)


  3. 동일한 클래스 내에서 @Transactional이 붙지 않은 상위 메소드가 @Transactional이 붙어 있는 하위 메소드를 호출할 경우 트랜잭션은 동작하지 않습니다. 1. 이 경우처럼 트랜잭션 관리가 어려운 이유는 , 트랜잭션이 동작하지 않는 오류를 프로그램 실행 전에는 알 수 없다는 점 때문입니다. 2. 스무스하게 프로그램은 실행되겠지만, 오류가 발생했을 경우 롤백이 되지 않는다던가 예기치 못한 데드락 상황을 겪게 될 수 있습니다.


  4. 테스트코드에서 @Transactional은 무조건 Rollback 만 일어난다는 점.


  5. @Transaction 에서 발생한 예외라고 해서 무조건 롤백은 아닙니다.
    rollback 예외 옵션을 통해 무시(?)하고 커밋을 할 수 도 있긴 합니다.




참고사이트 :

https://yeonyeon.tistory.com/223
https://goddaehee.tistory.com/167
https://joojimin.tistory.com/68

읽어주셔서 감사합니다.🖐


Written byRamsbaby
이 블로그는 직접 개발/운영하는 블로그이므로 당신을 불쾌하게 만드는 불필요한 광고가 없습니다.

#My Github#My Portfolio#Blog OpenSource Github#Blog OpenSource Demo Site