DEV/Spring Data JPA

데이터베이스 트랜잭션과 락 1

행운개발자 2024. 3. 18. 22:52
728x90

트랜잭션과 락

트랜잭션과 락이 비슷한 개념으로 헷갈릴 수 있지만 목적에서 분명한 차이가 있습니다. 트랜잭션은 데이터의 정합성을 보장해주는 기능이고 락은 동시성을 제어하기 위한 기능입니다.

트랜잭션은 작업의 완정성을 보장해줍니다. 논리적인 작업 셋을 모두 처리하거나 모두 처리되지 않도록 처리해줍니다.

락은 동시성을 제어하기 위한 기능입니다. 만약 하나의 회원 정보에 여러 커넥션이 동시에 접근했을 때 락이 없으면 데이터를 동시에 변경하게 됩니다. 락은 여러 커넥션이 동시에 동일한 자원을 요청할 때 한 시점에는 하나의 커넥션만 변경할 수 있도록 보장해주는 역할을 합니다.

더 나아가서, 트랜잭션의 격리 수준은 여러 트랜잭션 사이에서 작업 결과를 어떻게 공유하고 차단할 것인가를 결정하는 레벨입니다.

트랜잭션의 범위를 최소화 하라

트랜잭션의 개념에 대해서 간단하게만 이해하고 있을 때는 사용자의 요청은 곧 트랜잭션이라고 생각하기 쉽습니다. 그런데 하나의 사용자의 요청을 여러 개의 트랜잭션으로 나누어서 실행하는 것이 더 목적에 맞는 상황도 있습니다.

일반적으로 게시물을 등록하는 과정은 아래와 같이 나눠서 정리해볼 수 있습니다.

  1. 처리 시작
  2. 로그인 체크
  3. 글쓰기 내용의 오류 체크
  4. 업로드된 파일 확인 및 저장
  5. 사용자의 입력 내용을 DBMS에 저장
  6. 첨부 파일 정보를 DBMS에 저장
  7. 저장된 내용에 대한 정보를 DBMS에서 조회
  8. 게시물 등록에 대한 알림 메일을 발송
  9. 알림 메일 발송 이력을 DBMS에 저장
  10. 처리 완료

하나의 요청을 하나의 트랜잭션으로 처리하면 2~9번의 내용을 하나의 트랜잭션으로 처리하게 됩니다. 그런데 트랜잭션을 정교하게 관리하려면 5~6번에 대한 내용을 트랜잭션 A, 9번의 내용을 트랜잭션 B로 지정해야 합니다.

두 개의 트랜잭션 사이에 있는 알림 메일을 발송하는 작업은 외부의 네트워크를 통하는 작업이기 때문에 반드시 트랜잭션의 범위에서 제거하는 것이 좋습니다.

7번의 작업은 단순한 데이터 조회라고 생각하면 트랜잭션에 포함하지 않아도 됩니다.

 

ACID

  • 원자성 : Atomicity : 트랜잭션 내에서 실행한 작업들은 모두 성공하거나 모두 실패해야 한다.
  • 일관성 : Consistency : 모든 트랜잭션은 일관성 있는 데이터베이스 상태를 유지해야 한다. 예를 들어 무결성 제약 조건을 항상 만족해야 한다.
  • 격리성 : Isolation : 동시에 실행되는 트랜잭션들이 서로에게 영향을 미치지 않도록 격리해야 한다. (격리 수준)
  • 지속성 : Durability : 트랜잭션을 성공적으로 끝나면 항상 그 결과가 기록되어야 한다. 중간에 시스템에 문제가 발생해도 데이터베이스 로그 등을 사용해서 성공한 트랜잭션 내용을 복구해야 한다.

트랜잭션은 원자성, 일관성, 지속성을 보장합니다. 문제는 격리성입니다. 격리성을 완벽히 보장하려면 트랜잭션을 거의 순서대로 실행해야 합니다. 이렇게 되면 동시 처리 성능이 매우 나빠집니다. 이런 문제를 해결하기 위해 트랜잭션의 격리 수준은 4단계로 나누어 정의되어 있습니다.

 

격리 수준

  • READ UNCOMMITED
  • READ COMMITED
  • REPEATABLE READ
  • SERIALIZABLE

커밋과 롤백

데이터 변경 쿼리를 실행하고 데이터베이스에 그 결과를 반영하려면 commit을 호출해야합니다. 결과를 반영하고 싶지 않으면 롤백 명령어인 rollback을 호출하면 됩니다.

데이터베이스에 autocommit 변수를 true/false로 지정할 수 있습니다. 따라서 커밋이나 롤백을 직접 호출하지 않아도 됩니다. 하지만 쿼리를 하나하나 실행할 때마다 자동으로 커밋되어버리기 때문에 저희가 원하는 트랜잭션 기능을 제대로 사용할 수 없습니다.

보통 자동 커밋모드로 설정된 경우가 많기 때문에, 수동 커밋 모드로 설정하는 것을 트랜잭션의 시작이라고 표현할 수 있습니다. 수동 커밋 모드, 자동 커밋 모드는 한번 설정하면 해당 세션에서는 계속 유지됩니다. 중간에 변경할 수도 있습니다.

 

세션 A가 트랜잭션을 시작하고 데이터를 수정하고 아직 커밋을 실행하지 않았습니다. 이 때 세션 B에서 같은 데이터를 수정하게 되면 문제가 발생할 수 있습니다. 트랜잭션의 원장성을 보장하기 위해서 세션 A가 트랜잭션을 시작하고 데이터를 수정하는 동안에는 커밋이나 롤백 전까지 다른 세션에서 해당 데이터를 수정할 수 없게 막아야 합니다.

세션 A가 트랜잭션을 실행하는 동안 락이 걸리고, 세션 B가 같은 데이터에 대해서 접근할 때 락에 의해서 대기를 합니다. 세션 A의 트랜잭션이 종료될 때 데이터에 대한 락도 해소되고 세션 B의 트랜잭션이 다시 진행됩니다.

 

조회 락

데이터를 조회할 때에도 락을 획득하고 싶을 때가 있습니다. 이럴 때에는 select for update 구문을 사용하면 됩니다.

조회 락이 필요한 경우

트랜잭션 종료 시점까지 해당 데이터를 다른 곳에서 변경하지 못하도록 강제로 막아야할 때 사용합니다. 예를 들어 어떤 사용자의 계좌 잔고를 조회한 뒤, 이 잔고를 사용해서 어떤 계산을 수행합니다. 그런데 이 계산은 매우 중요한 계산이라서 계산을 완료할 때까지 사용자의 금액을 다른 곳에서 변경하면 안됩니다. 이럴 때 조회 시점에서 락을 획득하도록 select for update를 사용합니다. 실제로 락을 획득하는 세션에서 데이터를 사용만하고 변경하지 않아도 락을 획득할 수 있습니다.

728x90