트랜잭션(Transaction)과 격리 수준(Isolation Level)

정종일·2023년 2월 22일
0

Spring

목록 보기
2/18
💡 데이터베이스의 상태를 변환시키는 하나의 논리적 기능을 수행하기 위한 작업의 단위 한 번에 모두 수행되어야 하는 일련의 연산들을 의미

SQL 질의어인 SELECT, INSERT, UPDATE, DELETE문을 이용해 데이터베이스에 접근하는 것과 같지만 질의어는 한 문장이 아닐 수 있다.

트랜잭션의 특징

  1. 데이터베이스 시스템에서 병행 제어 및 회복 작업 시 처리되는 작업의 논리적 단위
  2. 사용자가 시스템에 대한 서비스 요구 시 시스템이 응답하기 위한 상태변환 과정의 작업단위
  3. 하나의 트랜잭션은 Commit 되거나 Rollback 됨

트랜잭션의 성질

원자성

  • 트랜잭션의 연산은 데이터베이스에 모두 반영되든지 전혀 반영되지 않아야 한다.
  • 트랜잭션 내의 모든 명령은 반드시 완벽히 수행되어야 하며, 모두가 완벽히 수행되지 않고 어느 하나라도 오류가 발생하면 트랜잭션 전부가 취소되어야 한다.

일관성

  • 트랜잭션이 그 실행을 성공적으로 완료하면 언제나 일관성 있는 데이터베이스 상태로 변환한다.
  • 시스템이 가지고 있는 고정요소는 트랜잭션 수행 전과 트랜잭션 수행 완료 후의 상태가 같아야 한다.

독립성

  • 둘 이상의 트랜잭션이 동시에 병행 실행되는 경우, 어느 하나의 트랜잭션 실행 중에 다른 트랜잭션의 연산이 끼어들 수 없다.
  • 수행중인 트랜잭션은 완전히 완료될 때까지 다른 트랜잭션에서 수행 결과를 참조할 수 없다.

영속성

  • 성공적으로 완료된 트랜잭션의 결과는 시스템이 고장나더라도 영구적으로 반영되어야 한다.

트랜잭션 연산

Commit 연산

💡 Commit 연산은 한 개의 논리적 단위에 대한 작업이 성공적으로 끝났고 데이터베이스가 다시 일관된 상태에 있을 때, 이 트랜잭션이 행한 갱신 연산이 완료된 것을 트랜잭션 관리자에게 알려주는 연산이다.

Rollback 연산

💡 Rollback 연산은 하나의 트랜잭션 처리가 비정상적으로 종료되어 데이터베이스의 일관성을 깨뜨렸을 때, 이 트랜잭션의 일부가 정상적으로 처리되었더라도 트랜잭션의 원자성을 구현하기 위해 이 트랜잭션이 행한 모든 연산을 취소하는 연산이다.
  • Rollback 시에는 해당 트랜잭션을 재시작하거나 폐기한다.

트랜잭션의 상태

활동(Active): 트랜잭션이 실행중인 상태

부분 완료(Partially Committed): 트랜잭션의 마지막 연산까지 실행했지만 Commit 연산이 실행되기 직전의 상태

완료(Committed): 트랜잭션이 성공적으로 종료되어 Commit 연산을 실행한 후의 상태

실패(Failed): 트랜잭션 실행에 오류가 발생하여 중단된 상태

철회(Aborted): 트랜잭션이 비정상적으로 종료되어 Rollback 연산을 수행한 상태


트랜잭션 격리 수준

개념

트랜잭션 격리수준이란 동시에 여러 트랜잭션이 처리될 때, 트랜잭션끼리 얼마나 서로 고립되어 있는지를 나타낸다. 각 단계에 따라 발생할 수 있는 부정합 문제가 다르다.

즉, 특정 트랜잭션이 다른 트랜잭션이 변경한 데이터를 볼 수 있도록 허용할지 말지를 결정하는 것이다.

격리수준의 종류를 알아보기 전에 먼저 발생할 수 있는 부정합 문제에 대해 살펴보자.

부정합 문제

더티 리드 (Dirty Read)

더티 리드는 다른 트랜잭션에서 처리한 작업이 완료되지 않았음에도 불구하고 다른 트랜잭션에서 볼 수 있게 되는 현상을 말한다.

A의 트랜잭션이 아직 커밋이 되지 않았음에도 불구하고 B가 조회한 결과는 삽입된 Jubal이 반환된다.

여기서 문제는 만약 A의 작업 도중 문제가 발생해 트랜잭션이 롤백되어도 B는 JuBal이 정상적인 데이터라고 판단해 작업을 계속 처리하게 된다는 것이다.

더티 리드가 허용되는 격리 수준은 READ UNCOMMITTED이고 해당 격리 수준은 피할 것이 권장된다.

그렇다면 더티 리드를 해결할 방법은 없을까?

Undo 로그 활용

READ COMMITTED 격리 수준에서는 Undo 로그를 활용해 더티 리드 문제를 해결한다.

Undo 영역은 UPDATE문이나 DELETE문 등을 활용해 데이터를 변경했을 때 변경되기 이전의 데이터를 보관하는 곳이다. INSERT문의 경우 해당 데이터의 row id를 저장하고 이를 활용해 물리적 메모리에 바로 접근할 수 있도록 보장한다.

  • 트랜잭션의 롤백 대비용
  • 트랜잭션의 격리 수준을 유지하며 높은 동시성 제공

NON-REPEATABLE READ

NON-REPEATABLE READ란 하나의 트랜잭션 내에서 동일한 SELECT 쿼리를 실행했을 때 항상 같은 결과를 보장해야 한다는 REPEATABLE READ 정합성에 어긋나는 것을 말한다.

READ COMMITTED 격리 수준을 사용한다면 REPEATABLE READ가 보장되지 않으므로 SELECT 쿼리를 통해 작업을 수행하는 로직에서 실행마다 다른 결과가 온다면 큰 문제가 발생할 수 있다.

NON-REPEATABLE READ 문제는 READ COMMITTED에서 더티 리드를 해결했듯이 Undo 영역을 통해 문제를 해결할 수 있는데 방식이 약간 다르다.

REPEATABLE READ 격리 수준에서는 Undo 영역에 백업된 레코드의 여러 버전 가운데 몇 번째 버전을 보여주냐에 차이가 있다.

즉, 언두 영역에 백업된 모든 데이터에는 변경이 발생한 트랜잭션의 번호가 포함되어 있는데, REPEATABLE READ 격리 수준에서는 실행중인 트랜잭션보다 작은 트랜잭션에서 변경한 데이터만 보게 하여 NON-REPEATABLE READ 문제를 해결한다.

PHANTOM READ

PHANTOM READ란 SELECT … FOR UPDATE 쿼리와 같이 쓰기 잠금을 거는 경우 다른 트랜잭션에서 수행한 변경 작업에 의해 레코드가 보였다 안 보였다 하는 현상을 말한다.

NON-REPEATABLE READ 문제 해결에서 언급한 것처럼 동일한 트랜잭션 내에서의 동일한 쿼리는 항상 같은 결과를 출력해야 한다. 하지만 위 그림에서 SELECT … FOR UPDATE 쿼리의 경우 SELECT하는 레코드에 쓰기 잠금을 걸어야 하는데, 언두 영역에는 잠금을 걸 수 없기 때문에 어쩔 수 없이 SELECT ... FOR UPDATE 나 SELECT ... LOCK IN SHARE MODE 로 조회되는 레코드는 언두 영역의 변경 전 데이터를 가져오는 것이 아니라 현재 레코드의 값을 가져온다.

InnoDB 스토리지 엔진에서 PHANTOM READ 해결

InnoDB 스토리지 엔진은 레코드 락과 갭 락을 합친 넥스트 키 락을 사용한다. t 테이블에 c1 = 13 , c = 17 인 두 레코드가 있다고 가정하자. 이때 SELECT c1 FROM t WHERE c1 BETWEEN 10 AND 20 FOR UPDATE 쿼리를 수행하면, 10 <= c1 <= 12, 14 <= c1 <= 16, 18 <= c1 <= 20 인 영역은 전부 갭 락에 의해 락이 걸려서 해당 영역에 레코드를 삽입할 수 없다. 또한 c = 13, c = 17인 영역도 레코드 락에 의해 해당 영역에 레코드를 삽입할 수 없다. 참고로 INSERT 외에 UPDATE, DELETE 쿼리도 마찬가지이다.

이러한 방식으로 InnoDB 스토리지 엔진은 넥스트 키 락을 이용하여 PHANTOM READ 문제를 해결한다.

격리수준(Isoation Level) 종류

READ UNCOMMITTED

  • 한 트랜잭션의 변경된 내용을 커밋이나 롤백과 상관없이 다른 트랜잭션에서 읽을 수 있는 격리 수준
  • 모든 부정합 문제 발생

READ COMMITTED

  • commit이 완료된 데이터만 조회 가능한 격리 수준
  • 더티리드 해결

REPEATABLE READ

  • 트랜잭션이 시작되기 전에 커밋된 내용에 관해서만 조회할 수 있는 격리 수준
  • NON-REPEATABLE-READ 해결
  • InnoDB에서는 PHANTOM READ 해결

SERIALIZABLE

  • 한 트랜잭션을 다른 트랜잭션으로부터 완전히 분리하는 격리 수준
  • 모든 부정합 문제 해결

출처: https://steady-coding.tistory.com/562

profile
제어할 수 없는 것에 의지하지 말자

0개의 댓글