1. 트랜잭션이란?


트랜잭션은 DB의 작업 처리 단위를 말합니다. 대표적인 예가 입출금인데 제가 홍길동에게 100원을 송금한다고 가정해보겠습니다. 

  1. 내 통장에서 100원이 감소한다.
  2. 홍길동 통장에 100원이 증가한다.

위 두 과정은 한 묶음으로 반드시 모두 실행됨을 보장하여야합니다. 내 통장에서는 100원이 감소되었는데, 홍길동 통장에는 100원이 증가되지 않는 불상사를 막기 위해서입니다. 이 묶음이 모두 정상적으로 실행되었다면 COMMIT을 해주고, 중간에 에러가 났다면 ROLLBACK을 하여 그동안의 작업을 모두 되돌립니다.

 

트랜잭션은 4가지의 특징을 가집니다. 이 4가지 특징의 앞글자만 따서 ACID라고 부릅니다.

  • 원자성(Atomicity): 트랜잭션은 더 이상 분해가 불가능한 최소한의 작업 처리 단위다.
  • 일관성(Consistency): 트랜잭션 실행의 결과는 일관성을 가진다. 
  • 격리성(Isolation): 실행 중인 트랜잭션의 중간 결과를 다른 트랜잭션이 접근할 수 없다.
  • 영속성(Durability): 트랜잭션이 실행을 성공적으로 완료하면 그 결과는 DB에 영속적으로 저장된다.

 

2. Transaction Isolation Level


위 트랜잭션의 4가지 특징 중 격리성(Isolation)을 보시면 실행 중인 트랜잭션의 중간 결과를 다른 트랜잭션이 접근할 수 없다고 되어있습니다. 중간 결과에 대해서 다른 트랜잭션이 어느 수준까지 접근할 수 없게끔 할 것이냐에 대해 레벨이 나뉘게 되는데요,

그 전에 격리성(Isolation)으로 인해 나타날 수 있는 문제점을 살펴보겠습니다. 총 3가지입니다.

  1. Dirty Read
    A세션에서 트랜잭션을 시작한 후 id=1인 Row를 DELETE하고 아직 커밋은 하지 않은 상황에서 B세션에서 트랜잭션을 시작한 후 해당 테이블을 SELECT하면 id=1인 Row가 삭제된 상태를 읽어옵니다. 만약 A세션이 롤백을 해버리면 B세션이 읽어들인 데이터는 정확하지 않게 됩니다. 
  2. Non-Repeatable Read
    한 트랜잭션 내에서 같은 Key를 가진 Row를 두 번 읽었는데 그 사이에 값이 변경 또는 삭제되어 결과가 다르게 나타나는 현상입니다.
  3. Phantom Read
    위 2번이랑 비슷한데, 한 트랜잭션 내에서 동일한 SELECT문을 두 번 실행했는데, 첫 번째 쿼리에서는 없었던 Row가 두 번째 실행결과에서 나타나는 현상입니다.

 

이어서 격리성 레벨을 살펴보겠습니다.

ANSI SQL 표준에서는 Transaction Isolation Level을 다음의 4단계로 정의합니다.

  1. Read Uncommitted
    한 트랜잭션이 id=1인 Row를 DELETE를 하고 아직 커밋은 하지않은 상황에서 다른 트랜잭션이 SELECT를 하면 변경된 내용(id=1인 Row가 삭제된 상태)으로 읽어들입니다. 정합성에 문제가 많은 격리 수준이기 때문에 권장되지 않습니다.
    (타 트랜잭션의 커밋되기 전 내용이 현재 트랜잭션에 즉각적으로 반영됩니다.)
  2. Read Committed
    A트랜잭션이 id=1인 Row를 UPDATE하면 기존 Row를 Undo 영역에 백업해둔 후 실행합니다. 아직 커밋은 하지않은 상황에서  B트랜잭션이 SELECT를 하면 Undo에 있는 Row를 읽어들입니다. 그러다가 A트랜잭션이 커밋을 하고 B트랜잭션에서 다시 SELECT하면 UPDATE된 Row를 읽어들입니다. (타 트랜잭션의 커밋이 현재 트랜잭션에 즉각적으로 반영됩니다.)
    격리성으로 인해 발생되는 3가지 문제점 중 Non-Repetable Read, Phantom Read 문제가 발생할 수 있습니다.
  3. Repetable Read (MySQL InnoDB엔진의 Default Isolation Level)
    이름에서 알 수 있듯이, 한 트랜잭션 내에서 반복해서 SELECT를 하더라도 결과는 항상 동일함을 보장합니다.
    A트랜잭션에서 id=1인 Row를 DELETE하고 커밋하지 않은 상황에서 B트랜잭션이 해당 테이블을 SELECT하면 id=1인 Row도 같이 조회됩니다. 이때 A트랜잭션이 커밋을 하고, 다시 B트랜잭션에서 SELECT하여도 마찬가지로 id=1인 Row가 조회됩니다.
    (타 트랜잭션의 실행 여부와 상관없이, 현재 트랜잭션이 시작될때의 데이터 상황을 끝까지 유지합니다.)
  4. Serializable Read
    가장 단순하고 엄격한 격리 수준으로 데이터 읽기/쓰기 작업에 Lock을 걸어 동시에 여러 트랜잭션이 접근하는걸 애시당초 막습니다.
    정합성에 가장 확실한 방법일 수 있겠으나, 동시처리가 불가능해져 성능 저하가 발생합니다. 

 

MySQL InnoDB엔진의 디폴트 격리 수준인 Repetable Read에 대해 자세히 살펴보겠습니다.

A세션, B세션 두 개의 세션을 가지고 예를 들어보겠습니다.

[그림 1] 한 트랜잭션 내에서의 작업이 다른 트랜잭션에 끼치는 영향

위 그림을 보시면, A세션에서 트랜잭션을 시작한 후 idx=1을 DELETE하였고 결과에서 idx=1 레코드가 정상적으로 삭제되었습니다.
이 상태에서 B세션으로 이동해 마찬가지로 트랜잭션을 시작하고 SELECT문만 실행시켰을 때의 결과를 보면 idx=1인 데이터도 아직 조회되는 것을 확인할 수 있습니다. 

 

자, 이 상황에서 B세션에서 idx=1을 DELETE하면 어떻게 될까요? (idx=1은 A세션에서 DELETE한 데이터라는걸 기억하세요!)

아래와 같이 계속 Running 상태에 있다가 Error code를 내뱉습니다.(idx=1을 UPDATE해도 마찬가지 결과입니다.)

[그림 2] A 트랜잭션에서 작업한 데이터를 B 트랜잭션에서 작업하고자 할때 걸리는 Lock 현상

위와 같은 상황이 발생하는 이유는 A세션에서 해당 Row를 DELETE함으로써 Exclusive Lock(쓰기 잠금)을 걸었기 때문입니다.
B세션이 Exclusive Lock이 걸린 Row에 또 다시 쓰기 작업을 하려 했기 때문에 에러가 났습니다.

B세션은 A세션에서 작업 중인 Row(Exclusive Lock)를 제외한 Row에는 어떠한 작업이든 할 수 있습니다.