일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 그리디
- Servlet
- 백준
- 김영한
- pointcut
- db
- springdatajpa
- 스프링 핵심 기능
- transaction
- JDBC
- JPQL
- java
- Greedy
- 스프링 핵심 원리
- 자바
- 인프런
- QueryDSL
- Spring Boot
- spring
- Android
- 스프링
- SpringBoot
- Exception
- Thymeleaf
- AOP
- jpa
- Proxy
- http
- kotlin
- 알고리즘
- Today
- Total
개발자되기 프로젝트
Transaction Manager-3 : Isolation 본문
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를 변경해도 기존과 동일한 내용만 읽는다.
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=?
그런데? 넘어가질 못한다?
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
'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 |