트랜잭션

아빠는 외계연·2022년 12월 21일
0

Study

목록 보기
2/11

트랜잭션이란?

  • 여러 SQL의 묶음을 의미한다.

연산

  • Commit : DB에 반영됨. 트랜잭션의 성공을 알림
  • Rollback : 문제 상황 발생 시 다시 원래 상태로 복귀
    • undo 영역
      • 커밋되기 전의 SQL들을 담는 공간이다.
      • Consistent Read를 제공한다.
    • redo 영역
      • 트랜잭션을 재실행하기 위함.
        -> 문제상황 발생 시 redo 영역을 통해 체크포인트로 이동 뒤 undo 영역으로 커밋되기 전 상황들을 롤백한다.

트랜잭션의 특성

ACID
A : 원자성 -> 한 트랜잭션은 All or Nothing. 전체 다 발생하던지 발생하면 안된다. 멱개의 연산만 실행되는 것은 용납할 수 없다.
C : Consistency -> 일관성. DB의 규칙에 맞게 데이터가 저장되어야 한다.
I : Independency -> 독립성. 각 트랜잭션은 독립적으로 진행되야 한다.
D : D -> 지속성. 데이터는 트랜잭션이 끝난 뒤에도 지속되야 한다.

트랜잭션 병행 처리 시 발생되는 문제점

  1. Dirty Read : 커밋되지 않은 데이터를 읽을 수 있는 것
  2. UnRepeatable Read : 한 트랜잭션에서 여러번의 SQL을 날렸을 때 값이 달라지는 것
  3. Phantom Read : 한 트랜잭션 내에서 두 번의 SQL을 날렸을 때 중간에 값이 생성되는 것

Consistent Read

  • 커밋된 후 특정 시점의 DB를 스냅샷으로 찍어서 보관하는 것
  • InnoDB는 즉시 갱신 회복 기법으로 커밋되지 않은 데이터를 DB에 반영하는 데 어떻게 이게 가능하냐?
    • InnoDB가 실행할 수 있는 방법: 모든 쿼리 후 결과를(커밋되지 않은 것들도)

트랜잭션 격리수준

  • Read_uncommitted : 다른 트랜잭션에서 커밋되지 않은 데이터도 읽을 수 있는 격리 수준. InnoDB에서는 즉시 갱신 회복 요법을 사용해서 커밋전에도 DB를 수정하기 때문에 DB값을 읽어오면 Dirty Read가 발생 가능하다.
    Consistent Read가 적용X
    Dirty read + Repeatable Read + Phantom Read모든 현상이 발생

  • Read_committed : 커밋된 데이터만 읽을 수 있는 격리수준. -> Dirty Read가 방지됨
    UnRepeatable Read는 실행됨
    -> Consistent Read가 select문이 발생할 때마다 적용되기 때문이다.
    따라서 다른 트랜잭션에서 update/delete 후 commit을 날리면 해당 값이 DB에 저장되고, select문이 commit된 데이터를 모두 들고오기 때문에 값이 다르게 보여지는 결과가 나타난다.

  • Repeatable Read : 트랜잭션의 아이디를 비교하여 현재 SQL문보다 앞선 데이터 값만 읽어오는 격리수준

    UnRepeatable Read가 방지된다 -> Consistent Read가 커밋 후 처음 select를 했을 당시를 남겨두기 때문에 commit전까지는 해당 스냅샷 결과만 보여지게 된다.
    Phantom Read는 방지가 안된다 -> select for update를 날릴 때 해당 레코드에만 lock이 걸리고 다른곳에는 안걸리는데, 따라서 insert는 가능하기 때문이다.
    하지만 innoDB는 next key lock = gap lock + record lock을 함께 걸어버리기 때문에 phantom read를 방지할 수 있다.
    또한 InnoDB의 Repeatable Read수준에는 select for share/update, update, delete시 넥스트 키 락을 걸어버린다 -> 하지만 update, delete는 공유 락이기 때문에 다른 트랜잭션에서 해당 필드를 조회는 가능하다.

  • Serializable : select 문을 select for share로 처리하여 어떠한 트랜잭션도 값을 변경할 수 없는 격리수준. 성능이 매우 떨어진다.

트랜잭션 전파레벨

  1. Mandatory : 부모가 무조건 존재해야 함. 아니면 에러
  2. Never : 트랜잭션이 존재하면 안됨
  3. Not-supported : 트랜잭션이 존재해도 생성X
  4. Requires_new : 새로운 트랜잭션을 생성. 하지만 자녀의 에러는 부모까지 전파된다.
    • 꼭 named lock을 사용할 때 해당 전파레벨로 처리해야 한다.
    • 왜냐하면 하나의 트랜잭션을 사용할 시 나올 때 바로 lock이 해제가 되는데, 이후 commit이 발생하기 때문이다.
    • 따라서 꼭 commit 후에 lock을 해제해야 하므로 새롭게 트랜잭션을 생성한다.
  5. Required : 부모 트랜잭션이 존재하면 해당 트랜잭션에 사용, 없으면 새로 생성
  6. nested : 병행해서 생성. 만약에 자식 트랜잭션 문제 시 자식만 롤백. 아니면 부모까지 롤백.
  7. Supported : 부모 트랜잭션이 있으면 사용. 없으면 생성X

병행 트랜잭션 처리

  1. locking 기법
  • optimistic lock
    • 버전 체크를 통해 해당 버전과 일치하면 데이터 변경, 불일치 시 데이터 변경X
    • 성능 좋음. 충돌 발생 시 개발자가 직접 에러 처리해야 함.
    • 충돌이 적게 발생하는 환경에서 유리
  • pesimistic lock
    • select for share, select for update -> 공유 락, 배타 락
    • 성능 저하. 충돌 발생 시 자동 롤백
    • 충돌이 다수 발생하는 환경에서 유리

@Transactional 동작원리

  • 해당 클래스에 대한 트랜잭션 기능이 적용된 프록시 객체가 생성된다.
  • 해당 어노테이션이 붙은 메서드가 호출될 경우 트랜잭션 시작, rollback, commit을 수행한다.
  • 정상여부는 default로 runtimeException을 제외하고 판단됨

@Transactional 사용 주의점

  • private는 @Transactional이 적용되지 않는다.
  • proxy형태로 동작하기 때문에 외부에서 접근이 가능한 메서드만 설정가능
    • 같은 클래스 내 여러 @Transactional 메서드 호출
  • 롤백을 해도 Auto_increment로 증가된 id는 다시 감소하지 않는다.
    • 트랜잭션 범위 밖에서 동작하기 때문
    • 왜? - 동시성 문제
      • rollback의 문제때문에 한 사용자가 다른 사용자의 가입을 기다려야 하는 상황이 발생할 수 있다.

프록시 패턴 *

  • 원래 객체를 감싸고 있는 객체
  • 프록시 구현체
    • JDK Proxy(Interface Based)
      • AOP를 적용하여 구현된 클래스의 인터페이스를 프록시 객체로 구현하여 코드를 끼워넣는 방식
        • 반드시 인터페이스를 구현해야하는 단점 존재
      • 자바에서 기본적으로 제공
    • CGLib Proxy(Subclass Based)
      • Spring Boot가 default로 사용
      • 인터페이스를 구현하지 않고 해당 구현체를 상속받는 것으로 문제를 해결
profile
Backend Developer

0개의 댓글