ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [스프링] 트랜잭션과 락,2차캐리
    스프링 2022. 12. 28. 18:24
    728x90
    김영한님의 자바 ORM 표준 JPA 프로그래밍에 기반되어 작성되었습니다

    트랜잭션은 ACID를 지켜야한다

    1.  원자성 : 하나의 작업처럼 모두 작업을 성공하거나 실패해야 한다

    2. 일관성 : 일관성 있는 DB 상태 유지 ( 무결성 제약 조건 항상 만족 ) 

    3. 격리성 : 트랜잭션들이 서로에게 영향을 미치지 못하도록 격리하기 

    4. 지속성 : 트랜잭션의 결과가 항상 기록 되어야한다 ( 중간에 시스템 문제가 생겨도 성공한 트랜잭션으로 복구 )

     

    문제는 격리성에서 발생한다!

     

    1. 격리 수준

    1. Read Uncommited : 커밋하지 않은 데이터를 읽을 수 있다 ( 모든 문제 발생 )
    2. Read Commited : 커밋한 데이터만 읽을 수 있다 ( Dirty Read 제외하고 2문제 가능 )
    3. Repeatable Read : 한번 조회한 데이터를 반복해서 조회해도 같은 데이터가 조회된다
    4. Serializable  : 가장 엄격한 격리수준의 트랜잭션이다
    * 1로 갈수록 동시성은 증가하지만, 문제가 많이 발생한다

     

    문제점

    1. Dirty Read : 트랜잭션 1이 수정하고 있는데 커밋하지 않아도 트랜잭션 2가 수정 중인 데이터 조회 가능
    2. Non-repeatable read : 트랜잭션 1이 조회중인데 트랜잭션2가 데이터를 수정하고 커밋하면 트랜잭션 1에서  수정된 데이터가 조회 된다
    3. Phantom read : 트랜잭션 1이 조회중인데 트랜잭션2가 데이터를 추가하고 커밋하면 트랜잭션 1에서 데이터가 추가된 데이터가 조회 된다

    Non-repeatable read VS Phantom read 
    Phantom read 은 데이터가 변경되지 않았지만, 이전보다 쿼리를 만족하는 데이터 수가 증가한단 점이다

     

    1차 캐리만 적절히 활용해도 트랜잭션이 Read Commited 수준이여도 repeatable read가 가능하다

    하지만, 더 높은 격리 수준을 원한다면 낙관적 락, 비관적 락을 사용하라

    JPA 사용시 추천 전략 : 낙관적 버전 관리 + Read Commited

     

    낙관적 락 : 트랜잭션 대부분은 충돌하지 않는다 가정하는 방법으로, JPA가 제공하는 버전 관리 기능 사용
    비관적 락 : 트랜잭션 대부분은 충돌한다 가정하는 방법으로, 우선 락을 걸고 보는 방법 ( select for update 구문 )

     

    또한, 두번의 갱신 분실 문제 ( DB 트랜잭션 범위를 넘어서는 문제 )도 있는데, 이는 사용자 A,B가 동시에 제목이 같은 공지사항을 수정할때 A가 먼저 완료 버튼을 누르고 B가 눌렀다고 가정하면 A의 수정사항은 날아가고 B만 남는다.

     

    DB 트랜잭션 범위를 넘어서기에 트랜잭션만으로는 문제를 해결할 수 없어서 선택할 수 있는 방법이 3가지가 있다

    1. 마지막 커밋만 인정하기 ( 기본적으로 사용함 )

    2. 최초 커밋만 인정하기

    3. 충돌하는 갱신 내용 병합하기

     

    1) 낙관적 락

    @Version 을 사용해서 버전 관리 기능을 추가해야 한다.

    @Version
    private Integer version;

    엔티티에 버전 관리횽 필드를 하나 추가하고 @Version을 붙이면 된다.

     

    엔티티를 수정할 때 마다 버전이 하나씩 자동 증가하는데, 수정할 때 조회 시점의 버전과 다르면 예외 발생

    version

    버전 정보를 사용하면 최초 커밋만을 인정하기가 적용된다

     

    버전은 엔티티의 값을 변경하면 증가하고, 이렇게 추가한 버전 관리 필드는 개발자가 임의로 수정하면 안된다

     

     

    2) 락 사용법

    1. 즉시 사용
    Board board = em.find(Board.class, id, LockModeType.OPTIMISTIC);
    
    2. 필요할때 락 사용
    Board board = em.find(Board.class, id);
    ...
    em.lock(board, LockModeType.OPTIMISTIC);

    참고로, 락 옵션없이 @Version만 있어도 적용된다

     

    락 옵션이 없으면(NONE) 조회 시점부터 수정 시점까지 엔티티가 변경되지 않음을 보장한다

    OPTIMISTIC은 조회 시점부터 트랜잭션 끝나는 시점까지 엔티티가 변경되지 않음을 보장한다

     

    2. 2차 캐시

    1차 캐시 - 영속성 컨택스트 내부에서 엔티티 보관하는 장소 ( 하지만, 트랜잭션 시작 ~ 종료 시점까지만 유효 )

        -> 동일성 보장, 영속성 컨텍스트 범위의 캐시

    2차 캐시 ( 공유캐시 )

        -> 동일성 보장안함, 애플리케이션 범위의 캐시 , 분산 캐시나 클러스터링 환경의 캐시는 애플리에케이션보다 더 오래 유지 가능

    2차 캐시 적용

     

    2차 캐시의 동작 과정

    1. 영속성 컨텍스트는 엔티티가 필요하면, 2차 캐시 조회

    2. 2차 캐시에 없으면 디비 조회

    3. 결과 2차 캐시에 보관

    4. 자신이 보관하는 엔티티를 복사해서 반환 -> 동일성 보장안하지만, 같은 객체를 동시에  수정하는 문제 해결

     

    Cacheable 어노테이션을 통해서 캐시 모드를 설정할 수 있다. 기본값은 true!

    '스프링' 카테고리의 다른 글

    [스프링] Gradle, Maven 차이점  (0) 2023.01.07
    [스프링] 예외처리, 성능 최적화  (2) 2022.12.29
    [스프링] OSIV  (2) 2022.12.28
    [스프링] 스프링 데이터 JPA  (0) 2022.12.28
    [스프링] 객체지향 쿼리 심화  (0) 2022.12.26
Designed by Tistory.