[Spring] MySQL에서 @Transactional(readOnly = true)

JeongYong Park·2023년 4월 17일
1

개요

트랜잭션을 적용할 때 DB에 쓰기 작업이 없을 때 readOnly 옵션을 true로 설정하는 경우가 많습니다. MySQL에서 이 옵션을 사용하면 얻는 이점이 궁금해 글을 쓰고자 합니다. (그런데 잡담이 더 많아진..)

트랜잭션

트랜잭션은 작업 수행의 가장 작은 논리적인 단위입니다. 또한 데이터베이스에서 작업의 완전성을 보장해줍니다. 트랜잭션은 ACID 라는 특징을 가지고 있는데 간단하게 정리하겠습니다.

  • Atomicity (원자성)
    • 하나의 트랜잭션은 모두 성공하거나 모두 실패해야 합니다. 만약 트랜잭션이 실패하면 모든 작업은 롤백되어야 합니다.
  • Consistency (일관성)
    • 트랜잭션이 실행되기 전과 후의 데이터베이스의 상태는 일관성이 있어야 합니다. 여기서 말하는 데이터베이스의 상태는 데이터베이스에 정의된 규칙과 제약조건을 의미합니다.
  • Isolation (독립성)
    • 하나의 트랜잭션은 다른 트랜잭션에 영향을 받지 않아야합니다.
    • 하지만 ACID는 완벽히 지켜지지 않는데, 이와 관련해서 Isolation-level 개념이 있습니다.
  • Durability (지속성)
    • 트랜잭션이 성공하면 해당 트랜잭션의 내용은 영구적으로 데이터베이스에 저장되어야 합니다.

스프링 프레임워크에서는 트랜잭션을 @Transactional 애노테이션을 통해 지원하고 있습니다.

@Transactional

@Transactional은 메서드 혹은 클래스 레벨에 적용될 수 있으며 해당 메서드에서 수행되는 모든 데이터베이스 관련 작업을 하나의 트랜잭션으로 처리하게 됩니다.

@Transactional은 메서드가 실행되는 동안 하나의 트랜잭션을 생성하고 해당 메서드가 완료되면 커밋(commit)을 수행하게 됩니다. 하지만 메서드가 실행 도중 예외(UncheckedException)을 던지면 트랜잭션은 롤백(rollback)됩니다.

의문점 : 왜 스프링은 UncheckedException이 발생했을 때 기본적으로 롤백하도록 했을까?
생각 : CheckedException은 예외처리를 강제하기 때문에 롤백하지 않고 UncheckedException은 예상하지 못한 예외이기 때문에 롤백하지 않을까?

물론 @Transactional의 rollbackFor, noRollbackFor 속성을 통해 롤백할 예외를 지정할 수 있습니다. 또한 rollbackForClassName이나 noRollbackForClassName 속성을 통해 예외 클래스 이름으로도 지정할 수 있습니다.

이러한 스프링의 @Transactional은 다양한 옵션을 가지고 있습니다.

  • propagation (트랜잭션의 전파 방식)
    • @Transactional이 달린 새로운 메서드를 호출할 때 새로운 트랜잭션을 시작하는 방식을 선택합니다. REQUIRED, REQUIRES_NEW, SUPPORTS, NOT_SUPPORTED, MANDATORY, NEVER 옵션이 존재합니다.
  • isolation (트랜잭션의 격리 수준)
    • 트랜잭션의 격리 수준을 지정할 수 있습니다. READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE 이 있습니다.
  • readOnly (읽기 전용 트랜잭션)
    • 읽기 전용 트랜잭션은 데이터를 읽기만 하고 쓰기 작업을 수행하지는 않습니다. 이 속성을 사용하면 성능이 향상됩니다.
  • timeout (타임아웃)
    • 트랜잭션의 타임아웃을 설정할 수 있습니다. 지정한 시간동안 작업이 완료되지 않으면 롤백합니다.

이 중 readOnly 옵션을 사용하면 왜 성능이 향상되는지 알아보고자 합니다.

readOnly = true를 쓰는 이유

더 나은 안정성: 읽기 전용 트랜잭션은 데이터를 변경하지 않으므로 예기치 않은 결과가 발생할 가능성이 줄어듭니다. 이로 인해 애플리케이션의 안정성이 높아질 수 있습니다. 스프링에서 @Transactional(readOnly = true) 옵션을 사용했을 때 해당 트랜잭션에서 데이터가 변경된다면 TransactionRequiredException이 발생합니다.

더 나은 가용성: 읽기 전용 트랜잭션은 더 빠르게 완료될 수 있으므로 애플리케이션의 가용성이 높아질 수 있습니다. 또한, 다른 트랜잭션과 충돌하지 않으므로 더 많은 클라이언트 요청을 동시에 처리할 수 있습니다.

위 두 개는 공통적입니다. 성능 향상의 이유는 DBMS마다 동작방식이 다른데 이 중 MySQL을 기준으로 알아보고자 합니다.

MySQL에서의 읽기 전용 트랜잭션

MySQL InnoDB에서는 읽기 전용 트랜잭션에 대하여 불필요한 트랜잭션 ID를 부여하지 않아도 되기 때문에 오버헤드가 발생하지 않아 성능의 이점을 얻을 수 있습니다.
트랜잭션 ID는 쓰기 작업이나 SELECT ... FOR UPDATE같은 locking read 작업을 수행할 때 부여됩니다. 이런 불필요한 트랜잭션 ID 부여 작업을 제거하면 쿼리 혹은 데이터 변경문이 read view를 구성할 때마다 참조되는 내부 데이터 크기를 줄이게 됩니다.

read view?
InnoDB의 MVCC에 의해 사용되는 내부 스냅샷

결론

  • 트랜잭션은 작업 수행의 가장 작은 논리적이 단위
  • 트랜잭션은 ACID 특징을 가지고 있습니다.
  • 스프링은 @Transacational을 통해 트랜잭션을 지원합니다.
  • 옵션 중 readOnly=true는 읽기 전용 트랜잭션을 지원합니다.
  • MySQL에서는 읽기 전용 트랜잭션의 경우 트랜잭션 ID를 부여하지 않아 불필요한 트랜잭션 ID 부여에 따른 오버헤드가 발생하지 않습니다.

참고자료

https://www.geeksforgeeks.org/acid-properties-in-dbms/
https://dev.mysql.com/doc/refman/8.0/en/glossary.html#glos_acid
https://dev.mysql.com/doc/refman/8.0/en/innodb-performance-ro-txn.html

profile
다음 단계를 고민하려고 노력하는 사람입니다

4개의 댓글

comment-user-thumbnail
2023년 4월 18일

많이 배우고 갑니다 교수님 👀

1개의 답글