데이터베이스

트랜잭션과 ACID

왜 필요한가

계좌 이체는 출금과 입금 두 개의 UPDATE로 이뤄진다. 출금은 성공했는데 입금 직전에 서버가 죽으면 돈이 사라진다. 두 문장을 “전부 되거나 전부 안 되는” 하나의 단위로 묶는 장치가 트랜잭션이다.

트랜잭션은 이 문제만 푸는 것이 아니다. 동시에 실행되는 다른 트랜잭션으로부터의 간섭, 커밋 이후의 유실까지 함께 다룬다. 무엇을 어디까지 보장하는지 구분하지 못하면, 트랜잭션으로 풀 수 없는 문제를 트랜잭션으로 풀려다 시간을 쓴다.

용어 정리

  • 트랜잭션: 하나의 작업 단위로 묶인 데이터베이스 연산의 묶음. BEGIN으로 시작해 COMMIT 또는 ROLLBACK으로 끝난다.
  • 커밋: 트랜잭션의 변경을 확정한다. 커밋 이후에는 다른 트랜잭션도 그 변경을 볼 수 있다.
  • 롤백: 트랜잭션의 변경을 전부 취소하고 시작 시점 상태로 되돌린다.
  • 자동 커밋(auto-commit): 문장 하나하나가 곧 하나의 트랜잭션이 되는 모드. 명시적으로 트랜잭션을 열지 않으면 대부분의 DBMS와 드라이버가 이 모드로 동작한다.

핵심 정리

트랜잭션이 보장하는 네 속성을 ACID라 부른다.

속성 보장하는 것 깨지면 생기는 일
Atomicity (원자성) 트랜잭션 안의 연산이 전부 반영되거나 전부 취소된다 출금만 되고 입금은 안 된 상태로 남는다
Consistency (일관성) 트랜잭션 전후로 데이터가 제약조건을 만족한다 잔액이 음수가 되거나 없는 회원을 참조하는 주문이 생긴다
Isolation (격리성) 동시에 실행되는 트랜잭션이 서로의 중간 상태를 보지 않는다 커밋되지 않은 값을 읽거나, 같은 조회의 결과가 도중에 바뀐다
Durability (지속성) 커밋된 변경은 장애가 나도 남는다 커밋 응답을 받은 주문이 재기동 후 사라진다

항목별 설명

Atomicity는 DBMS가 변경 전 값을 언두 로그(undo log)에 남겨 두고, 롤백 시 그것으로 되돌리는 방식으로 구현된다. “전부 아니면 전무”는 결과에 대한 약속이지, 중간에 실패하지 않는다는 약속이 아니다.

Consistency는 나머지 셋과 성격이 다르다. 원자성·격리성·지속성은 DBMS가 제공하지만, 일관성은 DBMS가 제공하는 제약조건(NOT NULL, UNIQUE, FK, CHECK)과 애플리케이션이 지키는 규칙이 함께 만든다. DBMS가 모르는 업무 규칙은 DBMS가 지켜주지 않는다.

Isolation은 넷 중 유일하게 단계를 고르는 속성이다. 완전한 격리는 비싸므로 대부분의 DBMS는 격리 수준을 낮춰 동시성을 얻는다. 어느 수준에서 무엇이 허용되는지가 격리 수준과 세 가지 이상 현상의 주제다.

Durability는 커밋 시점에 변경 내용을 리두 로그(WAL, redo log)에 먼저 기록하고 디스크에 동기화하는 방식으로 구현된다. 디스크 자체가 파손되는 경우까지는 막지 못하므로, 복제와 백업은 별도로 필요하다.

예시

계좌 이체를 명시적 트랜잭션으로 묶으면 두 UPDATE가 하나의 단위가 된다.

BEGIN;
UPDATE account SET balance = balance - 10000 WHERE id = 1;
UPDATE account SET balance = balance + 10000 WHERE id = 2;
COMMIT;

두 번째 UPDATE 직전에 장애가 나면 DBMS가 재기동하면서 이 트랜잭션을 롤백하므로, 첫 번째 UPDATE도 없던 일이 된다. 상태 변화는 다음 둘 중 하나뿐이다.

시점 1번 잔액 2번 잔액
시작 50000 30000
커밋 성공 40000 40000
롤백 50000 30000

같은 두 문장을 자동 커밋 모드로 실행하면 각각이 독립된 트랜잭션이 되어 “출금만 확정된” 세 번째 상태가 실제로 만들어진다.

혼동하기 쉬운 것

원자성과 격리성은 서로 다른 것을 막는다. 원자성은 트랜잭션이 절반만 반영되는 것을 막고, 격리성은 남의 트랜잭션 중간 상태가 나에게 보이는 것을 막는다. 원자성만 있고 격리성이 없으면, 커밋 전 절반만 반영된 상태를 다른 트랜잭션이 읽을 수 있다.

일관성(Consistency)과 CAP 정리의 C는 이름만 같고 다른 개념이다. ACID의 C는 한 노드 안에서 제약조건이 유지되는 것을, CAP의 C는 여러 노드가 같은 값을 보는 것을 뜻한다.

롤백과 예외 처리도 자동으로 연결되지 않는다. 애플리케이션이 예외를 잡아도 트랜잭션이 롤백 표시된 상태라면 커밋은 실패한다. Spring에서 이것이 어떻게 드러나는지는 예외를 잡았는데 왜 롤백될까에 있다.

더 깊이

참고