Notice
Recent Posts
Recent Comments
Link
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Archives
Today
Total
관리 메뉴

개발자되기 프로젝트

Transaction Manager-3 : Isolation 본문

JPA/영속성

Transaction Manager-3 : Isolation

Seung__ 2021. 7. 4. 22:07
 

Transaction Manager - 2

Transaction Manager - 1  1. Transaction이란? Data Base에서 다루는 개념. DB명령어들의 논리적인 묶음이다. Transaction의 성질 : ACID A : atomatic(원자성), 부분적의 성공을 허용하지 않는다. 중간 단계까..

bsh-developer.tistory.com

 

1. isolation : Transaction의 격리 수준.


 동시에 발생하는 tracsaction간에 data 접근을 어떻게 할지 정함.

 

5가지 Isolation 단계를 제공함.

LEVEL 격리단계 REMARK
- DEFAULT DB의 default 격리단계를 적용함
MySQL은 기본 격리단계 = REAPETABLE_READ
0 READ_UNCOMMITTED transaction에서 변경된 내용이  commit되기 전
다른 transaction에서 값을 읽을 수 있도록 허용
1 READ_COMMITTED commit되지 않은 변경 내용을 다른 transaction에서 읽는 것을 금지함.
2 REPEATABLE_READ 1. commit되지 않은 변경 내용을 다른 transaction에서 읽는 것을 금지함.
2. 첫 번째 transaction이 내용을 읽고 두 번째 transaction이 내용을 바꾸고
   첫 번재 transaction이 내용을 다시 읽을 때 두 번째 값과
   다른 값을 허용하지 않는다.
3 SERIALIZABLE 1. REPEATABLE_READ를 포함
2. 첫 번째 transaction이 WHERE조건을 만족하는 내용을 읽고
   두 번째 transaction이 WHERE조건을 만족하는 내용을 insert하고
   첫 번째 transation이 같은 조건으로 read할때 phanthom read를 금지

레벨이 높을 수록 격리 단계가 강력해지고 데이터 정합성 보장, 동시처리 성능이 떨어짐

레벨이 낮을수록 일부 데이터에 대해 정합성 보장하지 못함, 동시처리 성능 향상

 

 

2. REPEATABLE_READ


<BookService>에 method하나 추가해주자.

 

<Test> 새로운 isolationTest를 생성하자.

 

breakpoint에서 DB를 확인해보자.

 

 

 a) 1번 째 break point에서 transaction 생성

MySQL 콘솔에 아래와 같이 실행하자.

start transaction;

update book set category='none';

JPA의 Transaction(#1)이 진행하는 도중에 별도 transaction(#2)로 book의 category 값을 변경한다.

 

isolation이 dafault로 REPEATABLE_READ기 때문에 commit 되지 않은 내용을 다른 transaction에서 읽을 수 없다.

 

또한 처음 #1 Transaction이 읽은 내용이 #2 Transation이 변경한 후에 #1이 다시 읽은 내용과 다를 수 없다.

 

즉, #1 Transaction은 다른 Transaction이  data를 변경해도 기존과 동일한 내용만 읽는다.

category = 'none' 으로 변경됨, 기존엔 null이었음.

DB 콘솔에서 book의 정보를 불러오면 none으로 변경된 것을 확인할 수 있다.

 

#2 transaction 내 에서는 data가 변경이 되었지만, 실제로commit된 것은 아니다.

아직 다른 transaction에서 읽을 수 없다.

 

b) 2번 째 break point로 이동

 

bookRepository에 save가 진행되었다. #1 Transaction이 종료되지 않아 아직 commit은 되지 않은 상태이다.

 

log를 보면 category에 null로 입력이 되어있다.

>>>Optional[Book(super=BaseEntity(createdAt=2021-07-04T20:24:42.661311, updatedAt=2021-07-04T20:24:42.661311),
id=1, name=JPA 강의, authorId=null, category=null)]

 

 

 

 

DB console에서 commit을 시켜보자.

commit;

 

c) 다음 break point로 이동

아직 categoryr가 none으로 변경되지 않았다.(REPEATABLE_READ 특징)

>>>[Book(super=BaseEntity(createdAt=2021-07-04T20:24:42.661311, updatedAt=2021-07-04T20:24:42.661311), 
id=1, name=JPA 강의, authorId=null, category=null)]

 

d) 끝까지 실행

category가 none으로 변경되었다?

해당 LOG는 마지막 println()으로 출력된 결과이다.

 

println()은 별도 transaction(#3)이므로 #2에서 commit한 내용을 불러올 수 있다.

>>>>[Book(super=BaseEntity(createdAt=2021-07-04T20:24:42.661311, updatedAt=2021-07-04T20:24:42.661311), 
id=1, name=JPA 강의, authorId=null, category=none)]

 

3. READ_UNCOMMITTED


 @Transactional(isolation = Isolation.READ_UNCOMMITTED)
 public void get(Long id){

System.out.println(">>>" + bookRepository.findById(id));
System.out.println(">>>" + bookRepository.findAll());

System.out.println(">>>" + bookRepository.findById(id));
System.out.println(">>>" + bookRepository.findAll());
}

  - 1번 째 break point

DB 콘솔에서 아래와 같이 실행시키자.

start transaction;

update book set category='none';

 

  - 2번 째 break point

 

 

아직 commit 되지 않은 data를 읽어왔다! = dirty read

>>>Optional[Book(super=BaseEntity(createdAt=2021-07-04T21:02:31.303796, updatedAt=2021-07-04T21:02:31.303796),
id=1, name=JPA 강의, authorId=null, category=none)]

 

 

dirty read가 왜 문제가 되는지 알아보자.

DB를 rollback 시켜주자.

rollback;

method에 udpate를 추가해주자.

@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public void get(Long id){

System.out.println(">>>" + bookRepository.findById(id));
System.out.println(">>>" + bookRepository.findAll());

System.out.println(">>>" + bookRepository.findById(id));
System.out.println(">>>" + bookRepository.findAll());

Book book = bookRepository.findById(id).get();
book.setName("바뀌나?");
bookRepository.save(book);
}

- 1번째 break point에서 DB 콘솔에 입력(Transaction #2)

start transaction;

update book set category='none';

- 다음 break point로 이동해보자.

 

 마찬가지고 dirty read를 하고  update query가 실행되었다.

>>>Optional[Book(super=BaseEntity(createdAt=2021-07-04T21:12:34.387679, updatedAt=2021-07-04T21:12:34.387679), 
id=1, name=JPA 강의, authorId=null, category=none)]

Hibernate: 
    update
        book 
    set
        created_at=?,
        updated_at=?,
        author_id=?,
        category=?,
        name=?,
        publisher_id=? 
    where
        id=?

그런데? 넘어가질 못한다?

transactio lock

Transaction에서 lock이 발생하여 MySQL의 Transaction(#2)을 기다리는 중

 

DB console에서 commit을 시켜보자.

commit;

 

최종적으로 category는 none, 이름은 바뀌나? 로 변경되었다.

 

>>>>[Book(super=BaseEntity(createdAt=2021-07-04T21:20:36.778063, updatedAt=2021-07-04T21:21:01.056558),
id=1, name=바뀌나?, authorId=null, category=none)]

 

만약 commit하지 않고 rollback을 한다면?

예? category변경을 DB에서 rollback했는데, 반영이 되었다?

 

>>>>[Book(super=BaseEntity(createdAt=2021-07-04T21:22:37.820119, updatedAt=2021-07-04T21:22:58.317856),
id=1, name=바뀌나?, authorId=null, category=none)]

 

JPA의 업데이트의 특징이 반영된 것이다.

 

@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public void get(Long id){

System.out.println(">>>" + bookRepository.findById(id));
System.out.println(">>>" + bookRepository.findAll());

System.out.println(">>>" + bookRepository.findById(id));
System.out.println(">>>" + bookRepository.findAll());

Book book = bookRepository.findById(id).get();
book.setName("바뀌나?");
bookRepository.save(book);
}

 

uncommitted data를 가져옴. book entity는 umcommitted data를 가지고 잇음

 

 

그리고 update query를 보면 다른 값에 대해서도set이 진행된다.

 

따라서 dirty read로 가져온 값(none)이 entity에 존재를 하고 이 값을 가지고 set이 진행되어 update가 이루어짐.

 

DB 콘솔에서는 rollback했기 때문에 DB 콘솔에서 들어간 값이 아니라,

 

entity가 가지고 있는 값이 set을 통해 update가 된 것이다.

 

Hibernate: 
    update
        book 
    set
        created_at=?,
        updated_at=?,
        author_id=?,
        category=?,
        name=?,
        publisher_id=? 
    where
        id=?
Hibernate: 

 

의도치 않게 동작한 경우이다. 어떻게 해결할까???

 

 

Book class에 @DynamicUpdate를 추가해주자.

@DynamicUpdate : 변경된 columns에 대해서만 업데이트 진행. 

 

@DynamicUpdate
public class Book extends BaseEntity {

 

바로 전과 똑같이 commit 대신 rollback을 진행하자.

 

>>>[Book(super=BaseEntity(createdAt=2021-07-04T21:34:09.657526, updatedAt=2021-07-04T21:34:09.657526), 
id=1, name=JPA 강의, authorId=null, category=none)]

Hibernate: 
    update
        book 
    set
        updated_at=?,
        name=? 
    where
        id=?

>>>>[Book(super=BaseEntity(createdAt=2021-07-04T21:34:09.657526, updatedAt=2021-07-04T21:34:28.772018),
id=1, name=바뀌나?, authorId=null, category=null)]

 

이름은 JPA강의 -> 바뀌나? 로 변경되었다.

 

update query를 보면 이전과는 다르게 name만 set이 되었다.(update_at은 audit 때문 ㅎㅎ)

 

category의 경우 먼저 dirty read에 의해 none으로 출력되었지만, rollback이 진행되었다.

 

@DynamicUpdate에 의해 변경된 column(name)에 대해서만 update가 최종적으로 진행되었다.

 

이런 case는 data의 정합성에 문제를 발생시킬 수 있다.

 

따라서 실제 service에서는 read_uncommitted는 많이 사용하지 않음!

 --> read_commit으로 개선!

 

 

 

 

GitHub


 

bsh6463/BookManager

Contribute to bsh6463/BookManager development by creating an account on GitHub.

github.com

 

'JPA > 영속성' 카테고리의 다른 글

Transaction Manager - 5 : Propagation  (0) 2021.07.06
Transaction Manager - 4 : Isolation  (0) 2021.07.05
Transaction Manager - 2  (0) 2021.07.04
Transaction Manager - 1  (0) 2021.07.04
Entity 생애 주기  (0) 2021.07.04
Comments