Backend/DB

[DB] 조회 시 트랜잭션을 걸어야 하는 이유? (Feat. 트랜잭션 격리수준)

Emil :) 2024. 6. 26. 21:37
728x90
반응형

서론


면접에서 아래와 같은 이야기를 주고 받았다. (요즘 면접을 많이 다니기도 하고, 면접을 하면서 고민해볼 거리가 많이 나오는 것 같다.)

(프로젝트 이야기와 조회 로직에 트랜잭션을 붙이지 않았다는 이야기를 했음)
면접관 : 몇 가지 데이터 구조에 따라 다르긴 하겠지만, 조회 로직에 트랜잭션을 걸지 않았을 때 문제가 발생할 수 있을 것 같은데, 어떤 문제가 발생할 수 있을지 생각해보시겠어요?

트랜잭션은 업데이트에만 적용해놨었는데, 관련된 질문이 들어왔었다. 트랜잭션은 보통 동시성 이슈와 밀접한 연관이 있기 때문인데, 해당 질문엔 계좌 잔액 조회를 예시로 들어서 설명했다.

그러나 면접관이 원하는 대답은 아니었던 것 같았다. 조회엔 걸지 않는게 일반적인 걸로 알고있었는데.. 잘못된 거였구나. 라는 것을 깨닫고, 관련된 내용을 학습하면서 정리해보도록 하겠다.

레퍼런스들이 JPA랑 관련된 내용이 많아서, JPA를 제외하고 DB 트랜잭션 관점에서 보도록 하자. 트랜잭션 격리수준과도 밀접한 연관이 있으니, 관련된 내용도 같이 학습해보는 것이 좋다.

참고


https://www.inflearn.com/questions/40297/%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98-readonly-%EC%A7%88%EB%AC%B8-%EB%93%9C%EB%A6%BD%EB%8B%88%EB%8B%A4

 

트랜잭션 readonly 질문 드립니다. - 인프런

기선님 안녕하세요. 강의를 보던 중 트랜잭션 readonly 속성에 대해서 궁금증이 생겨서 질문 남겨 봅니다. 조회성 메소드에 @Transaction(readonly = true) 속성을 선언하면 내부적으로 플러시가 발생하지

www.inflearn.com

https://mangkyu.tistory.com/30

 

[Database] 8. 트랜잭션, 동시성 제어, 회복

[ 본 사진은 쉽게 배우는 오라클로 배우는 데이터베이스 개론과 실습 ppt에서 캡처했습니다. ]이번 장에서는 트랜잭션(Transaction), 동시성 제어(Locking or Currency Control), 회복(Recovery)에 대해 알아보겠

mangkyu.tistory.com

https://hungseong.tistory.com/74

 

@Transactional(readOnly = true)를 왜 붙여야 하나요

스프링으로 개발하면서 필연적으로 사용하게 되는 @Transactional. 우리는 스프링의 AOP를 통해 @Transactional 어노테이션만으로 손쉽게 Service Layer에서 트랜잭션을 걸 수 있다. 일반적으로, 조회용 메서

hungseong.tistory.com

https://hojak99.tistory.com/602

 

select 에도 transaction 걸어야 하는 이유

예전에 막 개발을 시작했을 때 spring 에서 @Tramsactional 이라는 어노테이션을 단순 select 에서도 적용하는 것을 보고 궁금했었다. 이번에 Real Mysql 책을 보다가 비슷한? 내용이 나왔다. 트랜잭션 내에

hojak99.tistory.com

 

본론


조회 시에도 트랜잭션을 걸어야 할까?

해당 질문에 대한 답은 비즈니스 로직에 따라 다르다. 트랜잭션 자체가 비용이 꽤 드는 작업이기 때문에, 정말 간단한 단순 조회 로직은 트랜잭션을 걸지 않는 것이 더 좋다.

그러나 대부분 프로덕트 레벨의 코드는 정말 "단순하게" 조회하진 않는다. 스프링/JPA를 사용한다면, @Transactional(readOnly=true) 처럼 조회만 하는 트랜잭션을 거는 것이 일반적이라고 한다.

조회 시 트랜잭션을 걸지 않으면 무슨 문제가 생길까?

그렇다면 단순 조회가 아닌 경우, 트랜잭션을 걸지 않으면 무슨 문제가 생길까?
관련해서 찾아본 결과, 대표적으로 3개 정도 문제가 있는 것을 알았다. 하나하나 예시를 들면서 소개해보겠다.

Dirty Read(더티 리드)

트랜잭션이 완료되지 않은 데이터를 다른 트랜잭션에서 읽었을 때, 실제와 다른 데이터가 조회되는 것을 말한다.
아래 예시를 들어보자.
1. 계좌에 78달러가 있다.
2. 전기세를 내야하는데, 45달러를 내야한다. 자동결제 시스템이 이를 처리하고 잔액을 업데이트했다.
3. 잔액은 33달러가 된다.
4. 이 시점에서, 사용자가 ATM을 통해 잔액이 33달러인 것을 확인했다.
5. 그러나 모종의 이유로 인해 결제 프로세스가 롤백되어, 다시 계좌 잔액이 78달러가 되었다.

 

출처: https://www.sqlshack.com/dirty-reads-and-the-read-uncommitted-isolation-level/

이 경우 더티 리드의 가장 대표적인 예시이다. (면접 때 이렇게 얘기했는데 좀 잘 풀어 말하진 못했던 것 같다.)
실제 상황이라면 사용자는 혼란스러울 것이다. 계좌 잔액 조회로 돈이 빠져나간 것을 확인했는데, 미납된 금액이 있다고 알림이 온다던지 한다면 말이다.

Non-Repeatable Read(논-리피터블 리드)

동일한 트랜잭션 내에서 조회 시, 항상 같은 결과가 나오지 않는 현상이다. 마찬가지로 예시를 들어보자.
트랜잭션 격리수준과 관련된 내용이 나오므로, 해당 내용은 여기를 참고하자.

1. id가 1인 유저의 age값을 조회한다. 20이 나온다.
2. 이 때, 조회가 완료되기 이전에 age값을 21로 업데이트 한다.
3. 동일한 트랜잭션 내에서 다시 id가 1인 유저를 조회했을 때, 트랜잭션 격리수준이 READ_UNCOMMITED, READ_COMMITED인 경우는 21로, REPEATABLE_READ, SERIALIZABLE은 20으로 조회된다.
출처: https://en.wikipedia.org/wiki/Isolation_%28database_systems%29#Non-repeatable_reads

이 경우, 트랜잭션의 격리 수준을 현재 비즈니스 로직에 알맞게 수정해 줄 필요가 발생한다.
일상 생활에서의 예를 들면 다음과 같겠다.

1. 쇼핑몰에서 관리자가 재고를 조회했는데, 10개가 남아있었다.
2. 중간에 다른 직원이 재고를 5개 추가한다.
3. 관리자가 재고를 다시 조회하면 15개로 나온다.

이처럼, 처음 조회한 값이 10인데, 다시 조회하면 15가 나오므로, 기존 값을 다시(Repeat) 조회할 수 없기 때문에 Non-Repeatable Read 라는 이름이 붙었다.

Phantom Read (팬텀 리드)

Non-Repeatable Read와 유사한데, 조금 다르다. 기존엔 존재하지 않았던 값이 조회되는 현상을 일컫는다.

마찬가지로 예시를 들어보자

1. 나이가 17 이상인 유저를 조회한다.
2. Alice와 Bob이 조회된다.
3. 중간에 Carol 을 집어넣는다.
4. SERIALIZABLE의 격리수준만 Alice와 Bob이 조회되고, 나머지는 Carol이 추가되어 조회된다.

 

https://en.wikipedia.org/wiki/Isolation_%28database_systems%29#Phantom_reads

마찬가지로 일상생활에서의 예시를 통해 조금 더 이해도를 높여보자.

1. 관리자가 예약 정보를 조회한다. 5개의 예약을 확인했다.
2. 중간에 고객이 예약을 추가하게 된다.
3. 관리자는 이전에 확인하지 못한 예약을 포함해 6개의 예약을 조회하게 된다.

Non-Repeatable Read vs Phantom Read?

레퍼런스를 찾던 도중, Non-Repeatable Read와 Phantom Read를 헷갈려하는 사람이 꽤 많은 것 같다....

예시를 찾아보는데 이런게 주르륵 나온다.

정리해보자면 다음과 같다. 다만, 격리레벨에 따라 조회되는 값이 달라지므로, 위에 첨부한 예제 스크린샷과 비교하며 이해하자.

  재조회 시 기존 값 재 조회 시 새로운 값
Non-Repeatable Read
( <= READ_COMMITED)
조회되지 않음 반영되어서 조회됨
Phantom Read
( < SERIALIZABLE )
조회 됨 조회 됨

 

결론


이처럼 조회 로직에도 트랜잭션과, 비즈니스 로직에 따라 격리 레벨을 다르게 설정해주는 것이 좋다는 것을 알게 되었다.

관련해서 @Transactional의 readOnly 옵션을 왜 거는지, 걸었을 때의 어떤 차이점이 있는지도 알아봤는데, 해당 내용은 다른 포스팅에서 한 번 다뤄보도록 하겠다.

구독 및 하트는 정보 포스팅 제작에 큰 힘이됩니다♡

728x90
반응형