글을 작성하게 된 계기
ISOLATION LEVEL을 관습적으로 사용하던 것을 느꼈고, 이를 정리하기 위해 글을 작성하게 되었습니다.
1. 왜 이런 고민을 하게 됐을까?
문제가 발생했던 것은 아닌데요, 스프링에서 @Transactional을 사용할 때, 관습적으로 REPEATABLE READ 만 사용하고 있는 것을 느꼈습니다. 별도 설정을 하지 않으면 MySQL은 기본적으로 REPEATABLE READ를 사용하기 때문입니다.
1
2
3
4
5
6
mysql> SHOW VARIABLES LIKE 'transaction_isolation';
+-----------------------+-----------------+
| Variable_name | Value |
+-----------------------+-----------------+
| transaction_isolation | REPEATABLE-READ |
+-----------------------+-----------------+
격리 수준을 별도로 지정하지 않을 경우, 데이터베이스의 기본 격리 수준 을 따르는 것을 볼 수 있습니다.
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
public abstract class DataSourceUtils {
......
@Nullable
public static Integer prepareConnectionForTransaction(Connection con, @Nullable TransactionDefinition definition) {
......
// Apply specific isolation level, if any.
Integer previousIsolationLevel = null;
if (definition != null && definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
if (debugEnabled) {
logger.debug("Changing isolation level of JDBC Connection [" + con + "] to " +
definition.getIsolationLevel());
}
int currentIsolation = con.getTransactionIsolation();
if (currentIsolation != definition.getIsolationLevel()) {
previousIsolationLevel = currentIsolation;
con.setTransactionIsolation(definition.getIsolationLevel());
}
}
return previousIsolationLevel;
}
}
이 과정에서 MySQL의 기본 격리 수준인 REPEATABLE READ 가 항상 최선일까 하는 의문이 들었습니다. 쓰기 작업 중 다른 트랜잭션이 값을 변경 하거나 팬텀 리드 가 발생할 일이 적다면 굳이 REPEATABLE READ를 사용할 필요가 없으니까요.
2. 어떻게 사용하는 것이 좋을까?
따라서 쓰기 작업에서 다른 트랜잭션이 값을 변경할 가능성이 없으며, 팬텀리드가 발생할 가능성이 적다면 READ COMMITTED 를 사용하기로 했습니다. 예를 들어, 사용자 정보 조회 같이요. 개인 정보는 본인만 변경하기 때문에 다른 쓰기 작업에서 값이 변경되거나 팬텀 리드도 거의 발생하지 않기 때문입니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Service
public class UserService {
......
@Transactional(isolation = Isolation.READ_COMMITTED)
public User findById(final Long userId) {
return userRepository.findById(userId);
}
@Transactional(isolation = Isolation.READ_COMMITTED)
public void updateUser(
final Long userId,
final UserInfoUpdateCommand command
) {
final User user = userRepository.findById(userId)
.orElseThrow(() -> new UserNotFoundException(userId));
......
}
}
3. 정리
기본적으로는 REPEATABLE_READ 를 사용하는 것이 좋습니다. 굳이 위험 리스크를 감수할 필요는 없으니까요. 다만 최근 대량 데이터를 다루면서 이런 사소한 포인트 하나가 전체 성능에 꽤 큰 영향을 미칠 수 있다는 것을 깨닫고 난 후, 세부 설정을 꼼꼼히 보는 습관이 생겼는데, 이런 경우도 있다 정도만 알고 있으면 좋을 것 같네요. 👀