트랜잭션(Transaction)이란?
“하나의 논리적 작업 단위를 구성하는 일련의 연산들의 집합”
“한 단위를 이루는 일련의 연관된 데이터베이스 연산(조작)”
트랜잭션은 왜 중요할까? 만약 트랜잭션을 하나씩 순차적으로, 한 트랜잭션 끝나고 다음 트랜잭션, … 과 같이 수행한다면, 프로그램의 속도가 매우 느려질 것이다. DBMS는 CPU보다 I/O 작업을 더 빈번하게 수행하기 때문이다. 따라서 트랜잭션을 순차적으로, 직렬로 수행할수록 CPU가 노는 시간(idle time)이 길어지는 것이고 이는 비효율적이다.
그러니 여러 트랜잭션들을 동시에 수행해야 하는데, 이 때 동시에 수행되는 트랜잭션들이 데이터베이스에 미치는 영향은 순차적으로 수행했을 때 데이터베이스에 미치는 영향과 차이점이 없어야만 한다. 즉 데이터베이스에 저장된 데이터의 무결성은 매우 중요한데, 이를 위한 가장 좋은 방법이 데이터베이스 트랜잭션을 사용하는 것이다. DBMS는 다수의 사용자가 데이터베이스를 동시에 접근하도록 허용하면서도, 데이터베이스의 일관성을 유지한다는 특징을 갖는다.
트랜잭션 관리(Transaction Management) 모듈은 병행 제어 모듈과 복구 모듈로 이루어져 있다.
- 병행 제어(Concurrency Control) 모듈 여러 사용자나 여러 응용 프로그램들이 동시에 수행되어도 서로 간섭하지 못하도록 보장하는 기법. 트랜잭션들이 동시에 수행되어도 각 트랜잭션이 고립적으로 수행된 것과 같은 결과를 보장한다.
- 복구(Recovery) 모듈 데이터베이스를 갱신하는 도중에 시스템이 고장나도 데이터베이스의 일관성이 유지되도록 하는 기법.
트랜잭션의 중요성을 이야기할 때 자주 등장하는 것이 은행 계좌의 입출금과 관련된 예시다. A 계좌에서 B 계좌로 송금을 한다고 생각해보자. 송금은 크게 2단계로 나뉜다. A 계좌에서 금액을 출금(차감)한 다음, 해당 금액을 B 계좌에 입금하는 절차를 거친다. 만약 A 계좌에서 출금한 직후에 뭔가 오류가 발생했는데, 그냥 그대로 끝나버리면? 실질적으로 B 계좌에 들어온 돈은 없는데 A 계좌의 잔고는 줄어드는 어이없는 일이 될 것이다. 시스템에서 해당 문제를 감지하고, 인출했던 돈을 다시 A 계좌로 입금시켜야 한다. 트랜잭션을 이용하면 이런 문제를 해결할 수 있다. 이러한 트랜잭션은 다양한 데이터 항목들을 액세스하고 갱신하는 프로그램 수행의 단위가 된다.
트랜잭션은 ACID라고 하는 다음의 네 가지 성질을 만족해야 한다.
- Atomicity(원자성)
- 트랜잭션의 모든 연산들이 정상적으로 수행 완료되거나, 아니면 전혀 어떠한 연산도 수행되지 않은 상태를 보장해야 한다. 한 마디로, ‘All or Nothing‘이다.
- 시스템이 다운되는 경우, DBMS의 복구 모듈은 트랜잭션에 의해 부분적으로 갱신된 작업을 취소하여 원자성을 보장한다.
- (계좌이체 예시 ⇒ 이체 과정 중에 트랜잭션이 실패하게 되어 예금이 사라지는 경우가 발생해서는 안 되기 때문에, DBMS는 완료되지 않은 트랜잭션의 중간 상태를 데이터베이스에 반영해서는 안 된다.)
- Consistency(일관성)
- 트랜잭션은 트랜잭션이 시작되기 전과 종료된 후에 데이터베이스가 올바르고 일관된 상태가 되도록 처리되어야 한다.
- 즉, 한 트랜잭션을 정확하게 수행하고 나면 데이터베이스가 하나의 일관된 상태에서 다른 일관된 상태로 바뀐다. 트랜잭션의 수행을 데이터베이스 상태 간의 전이(transition)로 봤을 때, 트랜잭션 수행 전후의 데이터베이스 상태는 각각 일관성이 보장되는 서로 다른 상태가 된다.
- 트랜잭션 수행이 보존해야 할 일관성은 기본 키, 외래 키 제약과 같은 명시적인 무결성 제약 조건들뿐만 아니라, 자금 이체 예에서 두 계좌 잔고의 합은 이체 전후가 같아야 한다는 사항과 같은 비명시적인 일관성 조건들도 있다.
- Isolation(독립성; 고립성)
- 여러 트랜잭션이 동시에 수행되더라도 각각의 트랜잭션은 다른 트랜잭션의 수행에 영향을 받지 않고 독립적으로 수행되어야 한다.
- 한 트랜잭션에서 데이터베이스를 변경한 내용은 트랜잭션이 커밋되기 전까지는 다른 어떤 질의나 트랜잭션과도 고립되어야만 한다. 즉 각 트랜잭션은 시스템 내에서 동시에 수행되고 있는 다른 트랜잭션들을 알지 못한다.
- 한 트랜잭션의 중간 결과가 다른 트랜잭션에게는 숨겨져야 한다는 의미인데, 이러한 isolation 성질이 보장되지 않으면 트랜잭션이 원래 상태로 되돌아갈 수 없게 된다.
- DBMS의 병행 제어 모듈이 트랜잭션의 고립성을 보장한다.
- Durability(지속성)
- 트랜잭션이 성공적으로 완료되어 커밋되고 나면, 해당 트랜잭션에 의한 모든 변경은 향후에 어떤 소프트웨어나 하드웨어 장애가 발생되더라도 영구적으로 보존되어야 한다.
- 데이터베이스 시스템은 데이터베이스의 현재 상태가 유실되지 않도록 시스템 충돌 등의 문제로부터 복구할 수 있는 방법을 갖추고 있어야 한다.
- DBMS의 복구 모듈은 시스템이 다운되는 경우에도 트랜잭션의 지속성을 보장한다.
다음 그림은 트랜잭션의 4가지 특성과, 이들을 지원하기 위한 DBMS의 기능들의 관계를 나타낸다.
- Commit(완료): 모든 작업이 성공적으로 처리되면, 모든 변경 내용을 한꺼번에 반영시킨다.
- Rollback(Abort; 철회): 한 트랜잭션에 속하는 작업 중 단 하나라도 실패하면, 트랜잭션 전체가 실패한 것으로 간주하고 그 트랜잭션에서 데이터베이스를 변경한 내용을 전부 원래대로 되돌린다.
병행 제어 (Concurrency Control)
- 직렬 스케줄(Serial Schedule)
여러 트랜잭션들을 한 트랜잭션 씩 차례대로 수행 - 비직렬 스케줄(Non-serial Schedule)
여러 트랜잭션들을 동시에 수행 - 직렬 가능(Serializable)하다
비직렬 스케줄의 결과가 어떤 직렬 스케줄의 수행 결과와 동등하다.
병행 제어를 하지 않고 다수의 트랜잭션을 동시에 수행할 경우 발생하는 문제
- Lost Update (갱신 손실)
수행 중인 트랜잭션이 갱신한 내용을 다른 트랜잭션이 덮어 씀으로써 갱신이 무효가 되는 것 - Dirty Read (더티 읽기)
완료되지 않은 트랜잭션이 갱신한 데이터 - Unrepeatable Read (반복할 수 없는 읽기)
한 트랜잭션이 동일한 데이터를 두 번 읽을 때, 서로 다른 값을 읽는 것
로킹
- 독점 로크(X-lock, eXclusive lock)
한 데이터 항목에 대한 갱신은 한 트랜잭션에 대해서만 허용되므로, 트랜잭션에서 갱신을 목적으로 데이터 항목을 접근할 때는 독점 로크를 요청 - 공유 로크(S-lock, Shared lock)
트랜잭션에서 읽을 목적으로 데이터 항목을 접근할 때는 공유 로크를 요청
로킹 기법을 사용해도 로크를 너무 일찍 해제한다거나 하면 일관성이 깨질 수 있다. 따라서 “2단계 로킹 프로토콜(2-phase locking protocol)”를 거쳐 로크 요청과 해제가 이루어져야 한다.
대부분의 로킹 관련 작업은 사용자가 신경 쓸 필요 없이, DBMS에서 모두 이루어진다.
복구 (Recovery)
여러 응용이 주기억장치 버퍼 내의 동일한 데이터베이스 항목을 갱신한 후에 디스크에 기록하는 것이, Disk I/O 작업을 줄일 수 있어 성능 향상에 도움이 된다. 즉 버퍼의 내용을 디스크에 기록하는 것을 최대한 줄이는 것이 일반적이다.
- REDO
만일 고장이 발생하기 전에 트랜잭션이 완료 명령을 수행했다면, 복구 모듈은 이 트랜잭션의 갱신 사항을 REDO(재수행)하여 트랜잭션의 갱신이 지속성을 갖도록 해야 한다. - UNDO
고장이 발생하기 전에 트랜잭션이 완료 명령을 수행하지 못했다면, 원자성을 보장하기 위해 이 트랜잭션이 데이터베이스에 반영했을 가능성이 있는 갱신 사항을 UNDO(취소)해야 한다.
로그, 체크 포인트 등 더 많은 내용은 하단의 NAVER D2 링크를 참조한다.
참고 자료
- 프로그래밍 면접 이렇게 준비한다(Programming Interviews Exposed Secrets To Landing Your Next Job, 3rd Edition)
- NAVER D2 — DBMS는 어떻게 트랜잭션을 관리할까?
- (Oracle을 기반으로 하는) 데이터베이스 배움터 제3판