Skip to main content

Transaction, ACID, Isolation Level

tip

TL;DR

  • Transaction: 데이터베이스에서 수행되는 논리적인 작업 단위
  • ACID: 신뢰성 있는 데이터베이스를 위해 트랜잭션에 필요한 4가지 속성
  • Atomicity: All or Nothing
    • Consistency: 트랜잭션의 결과는 데이터의 무결성을 보장해야 함
    • Isolation: 동시에 실행되는 트랜잭션은 서로 영향을 주면 안됨
    • Durability: 완료된 트랜잭션의 결과는 항상 유지되어야 함
  • Isolation Level: 동시에 수행되는 트랜잭션의 READ와 관련된 정책
    • Read Uncommitted: 커밋되지 않은 결과도 모두 조회
    • Read Committed: 커밋된 결과는 모두 조회 (트랜잭션 시작 시점 상관 X)
    • Repeatable Read: 동일한 트랜잭션이라면 조회 결과는 거의 같음 (INSERT 예외)
    • Serializable: 동일한 트랜잭션이라면 조회 결과는 항상 같음

데이터베이스에서의 트랜잭션이란?

데이터베이스의 트랜잭션에 대해 얘기하기 전에, 데이터베이스에서 수행되는 작업과, 데이터베이스의 신뢰성에 대해 먼저 생각해보자.

데이터베이스에서 수행되는 작업은 크게 두 가지로 나눌 수 있다. **읽는 작업(READ)**와 **쓰는 작업(WRITE)**이다. 그리고, “데이터베이스를 신뢰할만하다”라고 말하는 것은, 데이터베이스에 저장된 데이터들을 신뢰할 수 있다는 의미이다. 그렇다면, 다양한 요청에 의해 READ와 WRITE 작업들이 동시에 수행되는 환경 속에서, 데이터베이스를 신뢰할 수 있게 구축하기 위해서는 어떻게 해야 할까?

먼저, 요청되는 작업을 관리하기 위한 새로운 논리적인 단위가 필요하다. 이해를 위해 A계좌에서 B계좌로 입금하는 상황을 생각해보자. A계좌에서는 돈을 차감해야 하고(=WRITE), B계좌에는 돈을 추가해야 한다( =WRITE). 두 가지 WRITE 작업이 요청된 것이다. 이 때, 두 가지 작업을 묶어서 하나의 작업 단위로 관리하는 것이 데이터베이스를 신뢰성 있게 운영하는데 있어서 훨씬 수월할 것이다.

데이터베이스의 트랜잭션(Transaction)이란, 데이터베이스에서 수행되는 논리적인 작업 단위 를 의미한다. 하나의 트랜잭션에는 하나의 WRITE만 포함되어 있을 수도 있고, N개의 WRITE과 N개의 READ가 포함되어 있을 수도 있다.

또한, 신뢰성 있는 데이터베이스를 구축하기 위해서는 트랜잭션이라는 새로운 논리적인 작업 단위가 몇 가지 특징을 준수해야 한다. 이를 개념적으로 정리한 것이 1970년에 나온 “A Relational Model of Data for Large Shared Data Banks ”논문이다. 논문에서 직접적으로 ACID 라는 4가지 특징을 정의하지는 않았지만, 특징들에 대해 개념적으로 소개 되었다.

ACID이란?

ACID란, 신뢰성 있는 데이터베이스를 위해 트랜잭션에 필요한 4가지 특징을 의미한다. Atomicity(원자성), Consistency(일관성), Isolation(독립성), Durability(지속성)이다.

Atomicity

All or Nothing (=Commit or Rollback). 같은 트랜잭션 내에 있는 작업들은 모두 성공하거나, 모두 실패해야 한다. 트랜잭션은 여러 작업들을 묶어서 하나의 단위로 보기 때문에, atomicity(=원자성)이라고 부른다. A계좌에서 B계좌로 돈을 입금할 때, A계좌에서는 돈이 빠져나갔는데 B계좌에는 돈이 들어오지 않으면 큰일나고, 그 반대의 경우도 마찬가지이다. 이런 경우를 방지하고자, 트랜잭션은 atomicity 특징을 준수해야 한다. 이를 위해, 데이터베이스에서는 commit이나 rollback의 기능을 지원한다.

Consistency

트랜잭션 작업 이후, 데이터에 모순이 없어야 한다. A계좌에서는 돈이 인출 되었는데 B계좌에는 입금이 안 되었다거나, 돈을 인출한 이후의 A 계좌의 잔액이 음수라든가, Primary Key 규칙에 위반한다거나, Index가 설정된 column인데 반영이 안되었다거나 등, 기존에 정해져있던 규칙을 모두 따라야 한다 는 특징이다.

Isolation

트랜잭션이 한 번에 한 개씩만 실행된다면 상관 없겠지만, 대부분의 경우 다수의 트랜잭션이 동시에 요청될 것이다. 이 때, 서로 다른 트랜잭션이 서로에게 영향을 주면 안된다 는 특징이다. 다만, Isolation 정책은 데이터베이스의 성능과 밀접한 관계가 있어서 상황에 따라 유연하게 설정할 수 있다. 아래 기술된 Isolation level 부분을 참고하자.

Durability

트랜잭션이 완료된 이후에는, 변경 사항이 어떠한 상황(ex> 서버 재시작)에도 보존되어야 한다. 처리된 트랜잭션의 결과가 유실되어서는 안된다는 특징이다. 분산 시스템의 경우 데이터를 여러 노드에 복제해서 저장하는 방식으로 구현할 수 있다.

Isolation Level이란?

트랜잭션이 동시에(=병렬로) 실행될 때, 다른 트랜잭션에서 수행되는 작업의 중간 결과를 얼마나 반영할 것인가에 대한 정책이다. 이를 통해, 트랜잭션 내에서 어느 시점의 데이터를 READ 할 것인가 가 결정된다. 총 4가지(Read Uncommitted, Read Committed, Repeatable Read, Serializable)의 정책이 있다. 뒤로 갈수록 Isolation level이 올라가는 대신 동시성 처리 기능이 떨어진다. Trade-Off의 관계이기 때문에, 상황에 따라 적절히 선택해야 한다.

Read Uncommitted

다른 트랜잭션의 커밋되지 않은 작업에 대한 결과도 READ 한다. 트랜잭션 A, B가 있다고 하자. A에서 UPDATE, INSERT를 수행하고 아직 commit을 하지 않았더라도, B에서는 A에서 수행된 UPDATE, INSERT 결과를 조회할 수 있다. 트랜잭션 작업에 따른 변화를, 시간의 흐름에 따라 살펴보자.

  1. [A 트랜잭션] 시작
  2. [B 트랜잭션] 시작
  3. [A 트랜잭션] UPDATE, INSERT 수행
  4. [B 트랜잭션] 조회시 UPDATE, INSERT가 반영된 결과를 볼 수 있음

Read Committed

다른 트랜잭션에서 커밋한 결과를, 트랜잭션의 시작 시점과 상관 없이 모두 READ 한다. 트랜잭션 작업에 따른 변화를, 시간의 흐름에 따라 살펴보자.

  1. [B 트랜잭션] 시작
  2. [A 트랜잭션] 시작
  3. [A 트랜잭션] UPDATE, INSERT 수행
  4. [B 트랜잭션] 조회시 UPDATE, INSERT가 반영된 결과를 볼 수 없음
  5. [A 트랜잭션] COMMIT
  6. [B 트랜잭션] 조회시 UPDATE, INSERT가 반영된 결과를 볼 수 있음

Repeatable Read

MySQL의 기본 설정으로, 트랜잭션을 시작한 시점의 데이터를 항상 READ 한다. 따라서, 다른 트랜잭션에서 COMMIT이 되었어도, 현재 트랜잭션이 시작한 이후에 수행되었다면 READ하지 않는다. 다만, INSERT 작업은 볼 수 있는데, 이런 현상을 Phantom Read라고 부른다(MySQL은 거의 발생하지 않는다고 한다). 트랜잭션 작업에 따른 변화를, 시간의 흐름에 따라 살펴보자. 아래 과정에서 6번의 결과가 READ COMMITED와 차이가 발생하는 지점이다.

  1. [B 트랜잭션] 시작
  2. [A 트랜잭션] 시작
  3. [A 트랜잭션] UPDATE, INSERT 수행
  4. [B 트랜잭션] 조회시 UPDATE, INSERT가 반영된 결과를 볼 수 없음
  5. [A 트랜잭션] COMMIT
  6. [B 트랜잭션] 조회시 UPDATE는 볼 수 없고, INSERT는 볼 수 있음 (Phantom Read 발생).

Serializable

동일한 트랜잭션에서는 완벽히 같은 데이터를 READ 한다. 이름에서 알 수 있듯이 여러 트랜잭션이 요청된다 하더라도, 요청된 순서에 따라 순차적으로 수행한다. 다만, 성능이 많이 저하되기 때문에 보통의 상황에서는 사용하지 않는다.

MVCC 훑어보기

MVCC는 동시에 수행되는 트랜잭션의 데이터를 관리하기 위한 방법으로, RDMBS 종류에 따라 MVCC를 구현하는 방법은 다르다. 트랜잭션 ID, created timestamp, delete timestamp를 사용해서 관리한다. 또한, 트랜잭션이 많이 수행될수록 버전을 위해 MVCC를 위해 생성된 버전들을 관리해줘야 한다(ex> PostgreSQL의 VACUUM). 구체적인 내용은 다른 포스팅에서 다뤄볼 예정이다.

참고 자료