트랜잭션이란 한 개 이상의 SQL statement를 하나의 논리적 단위로 묶은 것이다. 트랜잭션이 어떤 상황에서도 의도한 대로 동작(일관성과 무결성 유지)하기 위해서는 고려해야 할 것이 있다. 첫 번째는 동시성이고 두 번째는 실패에 대한 대응이다.

일관성이란 여러 테이블에 걸친 데이터가 일관되어 실세계의 대상을 정확하게 표현해야 한다는 것이고 무결성은 일관, 정확성 등을 보장하게 위해 부여한 규칙이다.

ACID

트랜잭션은 ACID라고 불리는 다섯 가지 성질을 가진다.

  • Atomicity(원자성) - 트랜잭션은 더 이상 분해될 수 없는 하나의 작업이다. 트랜잭션의 결과는 트랜잭션을 이루는 모든 statement가 실행되지 않거나, 모두 성공적으로 실행되어야만 한다.

  • Consistency(일관성) - 고립상태(동시에 실행되는 트랜잭션이 없는 상황)에서 트랜잭션 수행이 데이터베이스의 일관성을 보존해야 한다. 데이터가 실세계의 대상을 정확하게 표현해야 한다.

  • Isolation(고립성) - 여러 트랜잭션이 동시에 수행되더라도 race condition이 발생하지 않아야 한다.

  • Durability(지속성) - 트랜잭션이 성공적으로 실행 완료되었다면 변경된 내용이 시스템에 오류가 발생하더라도 영구적으로 반영되어야 한다.

example

T를 계좌A에서 계좌B로 $50을 이체하는 트랜잭션이라고 하자. read와 write 연산은 데이터베이스를 즉시 갱신한다고 가정한다.

T : read(A);
    A := A - 50
    write(A);
    read(B);
    B := B + 50;
    write(B)
  • 일관성 - 두 계좌의 잔액의 합이 트랜잭션 수행 전과 후가 같아야 한다.
  • 원자성 - write(A) 연산과 write(B) 연산 사이에 오류가 발생한다면 write(A)는 취소되어야 한다. 즉 트랜잭션이 정상적으로 종료되지 못하면 DBMS는 로그를 사용해 데이터를 이전 값으로 북구하여 마치 트랜잭션이 전혀 실행되지 않았던 것처럼 보이게 한다.
  • 지속성 - 사용자가 트랜잭션이 정상적으로 종료되었음을 확인했다면 시스템 오류가 발생하더라도 그 트랜잭션이 처리한 모든 갱신 결과가 데이터베이스에 지속되어야 한다.
  • 고립성 - 각 트랜잭션의 원자성과 일관성이 보장된다고 해도 여러 트랜잭션이 동시에 수행되면 트랜잭션들을 구성하는 연산이 배치되는 순서에 따라 결과가 달라질 수 있다. (race condition이 발생할 수 있다.) 고립성이란 트랜잭션의 동시 실행 결과가 트랜잭션을 한 번에 하나씩 순차적으로 실행한 결과와 같다는 것을 보장하는 성질이다. 고립성은 DBMS의 동시성 제어 시스템(concurrency-control system)이 책임진다.

트랜잭션의 상태

트랜잭션은 반드시 다음 중 하나의 상태를 가진다.

  • 동작(Active) - 초기 상태. 트랜잭션이 실행 중이면 동작 상태라고 할 수 있다.
  • 부분 커밋(Partially committed) - 마지막 명령문이 실행된 후의 상태
  • 실패(Failed) - 정상적인 실행이 더 이상 진행될 수 없는 상태
  • 중단(Aborted) - 트랜잭션이 롤백되어 DB가 트랜잭션 시작 전 상태로 복구된 후의 상태
  • 커밋(Committed) - 트랜잭션이 성공적으로 완료된 후의 상태

직렬 가능성(Serializable)

동시에 실행되는 여러 트랜잭션의 결과가 해당 트랜잭션들을 한 번에 하나씩 순차적으로 실행했을 때의 결과와 같아지도록 하는 스케줄을 직렬 가능 스케줄이라고 한다. 스케줄이란 동시에 실행되는 트랜잭션들의 statement(명령어)를 실행할 순서이다.

명령어 I와 J중 어떤 것을 먼저 실행하느냐에 따라 그 결과가 달라지는 경우를 충돌(conflict)라고 한다. 스케줄 S가 충돌이 일어나지 않는 명령어들의 순서를 바꿔서 스케줄 S’로 변경된다면 S와 S’가 충돌 동등(conflict equivalent)하다고 말할 수 있다.

conflict equality

스케줄 S3과 S5는 충돌 동등하다. 즉, 두 스케줄은 동일한 최종 상태를 만든다.

충돌 직렬 가능성

스케줄 S가 한 직렬 스케줄과 충돌 동등하면 그 스케줄 S는 충돌 직렬 가능이라고 말한다.

schedule 1

S5에서 충돌하지 않는 명령어들의 순서를 바꾸다 보면 S1와 동일한 형태가 된다. 즉, S5는 S1과 충돌 동등하므로 충돌 직렬 가능성을 가진다.

schedule 7

반면 S7은 직렬 스케줄 <T1, T2> 또는 <T2, T1> 어느 쪽과도 동등하지 않기 때문에 충돌 직렬 가능하지 않다.

복구 가능성과 연쇄적 스케줄

복구 가능성

S9에서 T7은 T6가 기록한 데이터 A를 읽는다. T7의 결과는 T6에 따라 결정되기 때문에 T7은 T6에 종속적이다. 이 때 T7이 commit한 뒤 T6에서 오류가 발생했다면 원자성을 보장하기 위해 T6 뿐만 아니라 T7도 취소해야 한다. 그런데 T7은 이미 commit되었기 때문에 취소될 수 없다. 결국 T6의 실패로부터 올바로 복구할 수 없는 상황이다. S9은 복구 불가능한 스케줄이다. S9이 복구 가능하려면 T7은 T6가 커밋할때까지 커밋을 지연해야 한다.
복구 가능한 스케줄이(recoverable schedule)란 모든 트랜잭션 쌍 Ti와 Tj에 대해 Ti가 이전에 기록한 데이터 항목을 Tj가 읽었다면 Ti의 커밋 연산이 먼저 발생하는 스케줄이다.

스케줄이 복구 가능하더라도 트랜잭션 Ti의 실패로부터 올바르게 복구하기 위해 여러 트랜잭션을 되돌려야 할 수도 있다. S10에서 T8이 실패할 경우 T8은 당연히 롤백되어야 한다. T8에 종속적인 T9, T9에 종속적인 T10도 반드시 롤백되어야 한다. 이렇게 하나의 트랜잭션 실패로 인해 다른 일련의 트랜잭션이 롤백되는 현상을 연쇄적 롤백(cascading rollback)이라고 한다.

연쇄적 롤백은 비용이 큰 작업이다. 따라서 연쇄적 롤백이 발생하지 않도록 스케줄에 제한을 주어야 한다. 이러한 스케줄을 비연쇄적인 스케줄(cascadeless schedulh)이라고 한다. 비연쇄적인 스케줄에서 모든 트랜잭션 쌍 Ti, Tj에 대해 Ti가 기록한 데이터를 Tj가 읽기 전에 Ti의 커밋 연산이 먼저 실행된다. 모든 비연쇄적인 스케줄이 복구 가능함은 쉽게 증명이 가능하다.

트랜잭션 고립성 수준(Isolation Level)

모든 트랜잭션이 각자 혼자 수행될 때 데이터베이스의 일관성을 깨지 않는다면, 직렬 가능성은 이들을 동시에 수행해도 일관성을 유지할 수 있다는 것을 보장한다. 하지만 직렬 가능성을 보장하기 위한 규약으로 인해 동시성을 거의 허용하지 않는 경우도 생길 수 있다. 그렇게 될 경우 성능 향상과 utilization 향상이라는 장점이 사라진다.
SQL 표준에서 한 트랜잭션이 다른 트랜잭션들과 직렬 불가능한 방식으로 수행되는 것을 허용한다.트랜잭션의 직렬 가능성을 강제하지 않음으로써 굳이 직렬 가능성이 필요하지 않은 트랜잭션이 빠르게 실행되도록 할 수 있다.
예를 들어 수행 시간이 오래 걸리고 결과가 정확하지 않아도 괜찮은(최신 값이 아니여도 상관없는) 트랜잭션에 대해서는 커밋되지 않은 데이터 읽기라는 고립성 수준을 사용할 수 있다.

SQL 표준에 명시된 고립성 수준(isolation level)은 다음과 같다.

  • 직렬 가능(Serializable) - 직렬 가능한 실행을 보장한다. 데이터베이스의 일관성이 유지된다.
  • 반복 가능한 읽기(Repeatable read) - 단지 커밋된 레코드만 읽을 수 있고 한 트랜잭션이 한 레코드를 두 번 읽는 사이에 다른 트랜잭션이 그 코드를 갱신하지 못하도록 한다. 그러나 트랜잭션들은 직렬 가능하지 않을 수 있다. 예를 들어, 특정 조건을 만족하는 레코드를 검색할 때 커밋된 트랜잭션에 의해 삽입된 레코드 중 일부는 검색될 수도 있고 아닐 수도 있다.
  • 커밋된 데이터 읽기(Read committed) - 커밋된 레코드만 읽을 수 있지만, 반복 가능한 읽기는 요구하지 않는다. 예를 들어, 한 트랜잭션이 한 레코드를 두 번 읽는 사이에 그 레코드는 다른 완료된 트랜잭션들에 의해 갱신될 수 있다.
  • 커밋되지 않은 데이터 읽기(Read uncommitted) - 커밋되지 않은 데이터도 읽는다. SQL에서 허용하는 가장 낮은 수준의 고립성 수준이다. (일관성이 보장되지 않을 확률이 가장 높지만 속도는 빠르다.)

위의 모든 고립성 수준은 dirty write을 허용하지 않는다. 즉 커밋 또는 중단되지 않은 트랜잭션이 기록한 데이터 항목에 덮어쓰는 것은 허용하지 않는다.

많은 데이터베이스 시스템은 기본적으로 커밋된 데이터 읽기 고립성 수준으로 동작한다. 시스템의 기본 설정을 사용하지 않고 명시적으로 고립성 수준을 설정할 수 있다.

set transaction isolation level serializable

JDBC의 Connection 인터페이스의 setTransaction(int level) 메서드를 통해 다음과 같은 고립성 수준을 설정할 수 있다.

애플리케이션 프로그래머는 시스템의 성능을 높이기 위해 낮은 고립성 수준을 사용할 수 있다. 비일관성이 응용 프로그램에 영향을 미치지 않는다면 의미 있는 선택이다.

connection.setTransaction(Connection.TRANSACTION.SERIALIZABLE);
connection.setTransaction(Connection.TRANSACTION.REPEATABLE_READ);
connection.setTransaction(Connection.TRANSACTION.READ_COMMITTED);
connection.setTransaction(Connection.TRANSACTION.UNCOMMITTED);

references

Tags:

Categories:

Updated:

Comments